带有书签的Android WebView

时间:2020-02-23 14:29:24  来源:igfitidea点击:

为了将网页显示为应用程序的一部分,我们在应用程序中使用android WebView。
我们在这里介绍了WebView的基础知识。
在本教程中,我们将使用WebView实现加载进度条,并允许为URL加书签以便以后查看。
让我们开始吧。

Android WebView

AndroidWebView类是Android View类的扩展,它允许您将网页显示为活动布局的一部分。
要加载外部页面,我们在WebView实例上调用方法loadUrl(String url),并传入外部页面的url。
WebViewClient包含以下四个通常被覆盖的重要方法。

  • onPageStarted:顾名思义,该方法在开始加载URL时被调用。

  • shouldOverrideUrlLoading:每当单击来自已加载页面的内部链接时,都会调用此方法。
    对于API> 24,不建议使用" shouldOverrideUrlLoading(WebView视图,字符串url)",而应使用" shouldOverrideUrlLoading(WebView视图,WebResourceRequest请求)"。

  • onPageFinished:完全成功加载url时,将调用它

  • onReceivedError:未加载网址时,将调用此方法。

为了在webview上启用缩放控件,我们可以在webView实例上调用以下方法。

webView.getSettings().setSupportZoom(true);
webView.getSettings().setBuiltInZoomControls(true); //allow pinch to zooom
webView.getSettings().setDisplayZoomControls(false); //disable the default zoom controls on the page

让我们创建一个在显示ProgressBar的同时加载网页的应用程序。
我们将添加一项功能,使我们可以为URL添加书签并将其保存在SharedPreferences中,以供以后查看。

具有书签项目结构的Android WebView

我们已将"活动"类型选择为"导航抽屉"。

注意:如果您已将构建工具更新为API 26,并且遇到错误:"无法解决com.android.support:appcompat-v7:26.0.1",则需要在build.gradle文件,如下所示:

apply plugin: 'com.android.application'

allprojects {
  repositories {
      jcenter()
      maven {
          url "https://maven.google.com"
      }
  }
}

android {
  compileSdkVersion 26
  buildToolsVersion "26.0.1"
  defaultConfig {
      applicationId "com.theitroad.webviewwithbookmarks"
      minSdkVersion 16
      targetSdkVersion 26
      versionCode 1
      versionName "1.0"
      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  }
  buildTypes {
      release {
          minifyEnabled false
          proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
      }
  }
}

dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])
  androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
      exclude group: 'com.android.support', module: 'support-annotations'
  })
  compile 'com.android.support:appcompat-v7:26.0.1'
  compile 'com.android.support:design:26.0.1'
  compile 'com.google.code.gson:gson:2.7'
  compile 'com.android.support.constraint:constraint-layout:1.0.2'
  testCompile 'junit:junit:4.12'
}

注意:上面还添加了用于在"共享首选项"中保存书签网址的Gson库依赖项。

Android WebView书签代码

" activity_main.xml"布局的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:app="https://schemas.android.com/apk/res-auto"
  xmlns:tools="https://schemas.android.com/tools"
  android:id="@+id/drawer_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:fitsSystemWindows="true"
  tools:openDrawer="start">

  <include
      layout="@layout/app_bar_main"
      android:layout_width="match_parent"
      android:layout_height="match_parent" 

  <android.support.design.widget.NavigationView
      android:id="@+id/nav_view"
      android:layout_width="wrap_content"
      android:layout_height="match_parent"
      android:layout_gravity="start"
      android:fitsSystemWindows="true"
      app:headerLayout="@layout/nav_header_main"
      app:menu="@menu/activity_main_drawer" 

</android.support.v4.widget.DrawerLayout>

下面给出了app_bar_main.xml布局的代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:app="https://schemas.android.com/apk/res-auto"
  xmlns:tools="https://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.theitroad.webviewwithbookmarks.MainActivity">

  <android.support.design.widget.AppBarLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:theme="@style/AppTheme.AppBarOverlay">

      <android.support.v7.widget.Toolbar
          android:id="@+id/toolbar"
          android:layout_width="match_parent"
          android:layout_height="?attr/actionBarSize"
          android:background="?attr/colorPrimary"
          app:popupTheme="@style/AppTheme.PopupOverlay" 

  </android.support.design.widget.AppBarLayout>

  <include layout="@layout/content_main" 

</android.support.design.widget.CoordinatorLayout>

下面给出了content_main.xml的布局。
它包含一个用于启动其他活动的按钮。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:app="https://schemas.android.com/apk/res-auto"
  xmlns:tools="https://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  app:layout_behavior="@string/appbar_scrolling_view_behavior"
  tools:context="com.theitroad.webviewwithbookmarks.MainActivity"
  tools:showIn="@layout/app_bar_main">

  <Button
      android:id="@+id/btnLaunchWebsite"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="LAUNCH WEBSITE"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toTopOf="parent" 

</android.support.constraint.ConstraintLayout>

MainActivity.java的代码如下:

package com.theitroad.webviewwithbookmarks;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;

public class MainActivity extends AppCompatActivity
      implements NavigationView.OnNavigationItemSelectedListener {

  Button button;
  NavigationView navigationView;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      Toolbar toolbar = findViewById(R.id.toolbar);
      setSupportActionBar(toolbar);

      DrawerLayout drawer = findViewById(R.id.drawer_layout);

      button = findViewById(R.id.btnLaunchWebsite);
      button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              gotoBrowserActivity();
          }
      });

      ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
              this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
      drawer.addDrawerListener(toggle);
      toggle.syncState();

      navigationView = findViewById(R.id.nav_view);
      navigationView.setNavigationItemSelectedListener(this);
  }

  @Override
  public void onBackPressed() {
      DrawerLayout drawer = findViewById(R.id.drawer_layout);
      if (drawer.isDrawerOpen(GravityCompat.START)) {
          drawer.closeDrawer(GravityCompat.START);
      } else {
          super.onBackPressed();
      }
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
      //Inflate the menu; this adds items to the action bar if it is present.
      getMenuInflater().inflate(R.menu.main, menu);
      return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
      //Handle action bar item clicks here. The action bar will
      //automatically handle clicks on the Home/Up button, so long
      //as you specify a parent activity in AndroidManifest.xml.
      int id = item.getItemId();

      //noinspection SimplifiableIfStatement
      if (id == R.id.action_settings) {
          return true;
      }

      return super.onOptionsItemSelected(item);
  }

  @Override
  public boolean onNavigationItemSelected(MenuItem item) {
      //Handle navigation view item clicks here.
      int id = item.getItemId();

      if (id == R.id.nav_home) {
          navigationView.getMenu().getItem(0).setChecked(false);
      } else if (id == R.id.nav_bookmark) {
          navigationView.getMenu().getItem(1).setChecked(false);
          startActivity(new Intent(this, BookmarkActivity.class));
      }
      DrawerLayout drawer = findViewById(R.id.drawer_layout);
      drawer.closeDrawer(GravityCompat.START);
      return true;
  }

  private void gotoBrowserActivity() {
      startActivity(new Intent(this, BrowserActivity.class));
  }
}

在上面的代码中,我们在NavigationDrawer中定义了两个菜单选项(文件位于菜单文件夹中,名称为" activity_main_drawer.xml")。

单击MainActivity.java中的按钮将启动BrowserActivity.java,然后单击Bookmark菜单按钮将启动BookmarkActivity.java,我们将很快看到。

在您的AndroidManifest.xml中添加以下访问互联网的权限。

<uses-permission android:name="android.permission.INTERNET"

下面给出了" activity_browser.xml"的代码。

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:app="https://schemas.android.com/apk/res-auto"
  android:id="@+id/main_content"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/white"
  android:fitsSystemWindows="true">

  <android.support.design.widget.AppBarLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:theme="@style/AppTheme.AppBarOverlay">

      <android.support.v7.widget.Toolbar
          android:id="@+id/toolbar"
          android:layout_width="match_parent"
          android:layout_height="?attr/actionBarSize"
          android:background="?attr/colorPrimary"
          app:popupTheme="@style/AppTheme.PopupOverlay" 

  </android.support.design.widget.AppBarLayout>

  <android.support.v4.widget.NestedScrollView xmlns:android="https://schemas.android.com/apk/res/android"
      xmlns:app="https://schemas.android.com/apk/res-auto"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:fadeScrollbars="false"
      android:scrollbarFadeDuration="0"
      app:layout_behavior="@string/appbar_scrolling_view_behavior">

      <WebView
          android:id="@+id/webView"
          android:layout_width="match_parent"
          android:layout_height="wrap_content" 

  </android.support.v4.widget.NestedScrollView>

  <ProgressBar
      android:id="@+id/progressBar"
      style="@style/Widget.AppCompat.ProgressBar.Horizontal"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginTop="-7dp"
      android:indeterminate="true"
      android:visibility="gone"
      app:layout_behavior="@string/appbar_scrolling_view_behavior" 

</android.support.design.widget.CoordinatorLayout>

下面给出了" BrowserActivity.java"的代码:

package com.theitroad.webviewwithbookmarks;

import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.util.ArrayList;

public class BrowserActivity extends AppCompatActivity {

  public static final String PREFERENCES = "PREFERENCES_NAME";
  public static final String WEB_LINKS = "links";
  public static final String WEB_TITLE = "title";

  WebView webView;
  private ProgressBar progressBar;
  String current_page_url = "https://www.wikipedia.com";
  CoordinatorLayout coordinatorLayout;

  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_browser);

      Toolbar toolbar = findViewById(R.id.toolbar);
      setSupportActionBar(toolbar);
      getSupportActionBar().setTitle("");
      toolbar.setNavigationIcon(R.drawable.ic_arrow_back);
      toolbar.setNavigationOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              onBackPressed();
          }
      });

      if (getIntent().getExtras() != null) {
          current_page_url = getIntent().getStringExtra("url");
      }

      webView = findViewById(R.id.webView);
      progressBar = findViewById(R.id.progressBar);
      webView.loadUrl(current_page_url);
      initWebView();

      coordinatorLayout = findViewById(R.id.main_content);
  }

  private void initWebView() {
      webView.setWebViewClient(new WebViewClient() {
          @Override
          public void onPageStarted(WebView view, String url, Bitmap favicon) {
              super.onPageStarted(view, url, favicon);
              progressBar.setVisibility(View.VISIBLE);
              current_page_url = url;
              invalidateOptionsMenu();
          }

          @Override
          public boolean shouldOverrideUrlLoading(WebView view, String url) {
              webView.loadUrl(url);
              return true;
          }
          
          @Override
          public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                  webView.loadUrl(request.getUrl().toString());
              }
              return true;
          }

          @Override
          public void onPageFinished(WebView view, String url) {
              super.onPageFinished(view, url);
              progressBar.setVisibility(View.GONE);
              invalidateOptionsMenu();
          }

          @Override
          public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
              super.onReceivedError(view, request, error);
              progressBar.setVisibility(View.GONE);
              invalidateOptionsMenu();
          }
      });

      webView.getSettings().setLoadWithOverviewMode(true);
      webView.getSettings().setUseWideViewPort(true);
      webView.clearCache(true);
      webView.clearHistory();
      webView.getSettings().setJavaScriptEnabled(true);
      webView.setHorizontalScrollBarEnabled(true);
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
      //Inflate the menu; this adds items to the action bar if it is present.
      getMenuInflater().inflate(R.menu.browser, menu);

      SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
      String links = sharedPreferences.getString(WEB_LINKS, null);

      if (links != null) {

          Gson gson = new Gson();
          ArrayList<String> linkList = gson.fromJson(links, new TypeToken<ArrayList<String>>() {
          }.getType());

          if (linkList.contains(current_page_url)) {
              menu.getItem(0).setIcon(R.drawable.ic_bookmark_black_24dp);
          } else {
              menu.getItem(0).setIcon(R.drawable.ic_bookmark_border_black_24dp);
          }
      } else {
          menu.getItem(0).setIcon(R.drawable.ic_bookmark_border_black_24dp);
      }
      return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {

      if (item.getItemId() == R.id.action_bookmark) {

          String message;

          SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
          String jsonLink = sharedPreferences.getString(WEB_LINKS, null);
          String jsonTitle = sharedPreferences.getString(WEB_TITLE, null);

          if (jsonLink != null && jsonTitle != null) {

              Gson gson = new Gson();
              ArrayList<String> linkList = gson.fromJson(jsonLink, new TypeToken<ArrayList<String>>() {
              }.getType());

              ArrayList<String> titleList = gson.fromJson(jsonTitle, new TypeToken<ArrayList<String>>() {
              }.getType());

              if (linkList.contains(current_page_url)) {
                  linkList.remove(current_page_url);
                  titleList.remove(webView.getTitle().trim());
                  SharedPreferences.Editor editor = sharedPreferences.edit();
                  editor.putString(WEB_LINKS, new Gson().toJson(linkList));
                  editor.putString(WEB_TITLE, new Gson().toJson(titleList));
                  editor.apply();

                  message = "Bookmark Removed";

              } else {
                  linkList.add(current_page_url);
                  titleList.add(webView.getTitle().trim());
                  SharedPreferences.Editor editor = sharedPreferences.edit();
                  editor.putString(WEB_LINKS, new Gson().toJson(linkList));
                  editor.putString(WEB_TITLE, new Gson().toJson(titleList));
                  editor.apply();

                  message = "Bookmarked";
              }
          } else {

              ArrayList<String> linkList = new ArrayList<>();
              ArrayList<String> titleList = new ArrayList<>();
              linkList.add(current_page_url);
              titleList.add(webView.getTitle());
              SharedPreferences.Editor editor = sharedPreferences.edit();
              editor.putString(WEB_LINKS, new Gson().toJson(linkList));
              editor.putString(WEB_TITLE, new Gson().toJson(titleList));
              editor.apply();

              message = "Bookmarked";
          }

          Snackbar snackbar = Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG);
          snackbar.show();

          invalidateOptionsMenu();
      }

      return super.onOptionsItemSelected(item);
  }

  @Override
  public void onBackPressed() {
      if (webView.canGoBack()) {
          webView.goBack();
      } else {
          super.onBackPressed();
      }
  }

}

在上面的代码中,我们将URLhttps://www.wikipedia.com加载到WebView中。

分别在加载和完成URL时显示和隐藏ProgressBar。

菜单从browser.xml文件中被放大,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:app="https://schemas.android.com/apk/res-auto">
  <item
      android:id="@+id/action_bookmark"
      android:icon="@drawable/ic_bookmark_black_24dp"
      android:orderInCategory="100"
      android:title="BOOKMARK"
      app:showAsAction="always" 

</menu>

在" onCreateOptionsMenu()"中,我们检查current_page_url是否已存在于我们的SharedPreferences中。
根据结果,我们显示相关的书签菜单图标。

onOptionsItemSelected()中,我们根据是否存在URL存储或者从SharedPreferences中删除该URL。

SharedPreferences以Gson字符串的形式存储链接的ArrayList和相应的网页标题,这些字符串最终将显示在" BookmarkActivity.java"中,我们将在下面进行讨论。

invalidateOptionsMenu()用于重新绘制工具列中的菜单。

如果用户通过使用canGoBack()和goBack()检查并返回来单击WebView中的任何内部链接,则使用onBackPressed()来浏览网页。

下面给出了" activity_bookmark.xml"的代码。

<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:app="https://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.v7.widget.Toolbar
      android:id="@+id/toolbar"
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      android:layout_alignParentTop="true"
      android:background="?attr/colorPrimary"
      app:popupTheme="@style/AppTheme.PopupOverlay" 

  <android.support.v4.widget.SwipeRefreshLayout
      android:id="@+id/swipeToRefresh"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_below="@+id/toolbar"
      android:layout_margin="@dimen/fab_margin">

      <ListView
          android:id="@+id/listView"
          android:layout_width="match_parent"
          android:layout_height="wrap_content" 

  </android.support.v4.widget.SwipeRefreshLayout>

  <LinearLayout
      android:id="@+id/emptyList"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_centerInParent="true"
      android:gravity="center"
      android:orientation="vertical"
      android:visibility="gone">

      <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_gravity="center"
          android:gravity="center"
          android:text="WHOOPS"
          android:textColor="#212121"
          android:textSize="20sp"
          android:textStyle="bold" 

      <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_gravity="center"
          android:gravity="center"
          android:text="There are no bookmarks at the moment"
          android:textColor="#212121" 

  </LinearLayout>

</RelativeLayout>

下面给出了BookmarkActivity.java的代码。

package com.theitroad.webviewwithbookmarks;

import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import static com.theitroad.webviewwithbookmarks.BrowserActivity.PREFERENCES;
import static com.theitroad.webviewwithbookmarks.BrowserActivity.WEB_LINKS;
import static com.theitroad.webviewwithbookmarks.BrowserActivity.WEB_TITLE;

public class BookmarkActivity extends AppCompatActivity {

  ArrayList<HashMap<String, String>> listRowData;

  public static String TAG_TITLE = "title";
  public static String TAG_LINK = "link";

  ListView listView;
  ListAdapter adapter;
  LinearLayout linearLayout;
  SwipeRefreshLayout mSwipeRefreshLayout;

  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_bookmark);

      Toolbar toolbar = findViewById(R.id.toolbar);
      setSupportActionBar(toolbar);
      getSupportActionBar().setTitle("BOOKMARKS");
      toolbar.setNavigationIcon(R.drawable.ic_arrow_back);
      toolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
      toolbar.setNavigationOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              onBackPressed();
          }
      });

      listView = findViewById(R.id.listView);
      linearLayout = findViewById(R.id.emptyList);

      mSwipeRefreshLayout = findViewById(R.id.swipeToRefresh);
      mSwipeRefreshLayout.setColorSchemeResources(R.color.colorAccent);
      mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
          @Override
          public void onRefresh() {
              new LoadBookmarks().execute();

          }
      });

      new LoadBookmarks().execute();
      listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

          public void onItemClick(AdapterView<?> parent, View view,
                                  int position, long id) {

              Object o = listView.getAdapter().getItem(position);
              if (o instanceof Map) {
                  Map map = (Map) o;
                  Intent in = new Intent(BookmarkActivity.this, BrowserActivity.class);
                  in.putExtra("url", String.valueOf(map.get(TAG_LINK)));
                  startActivity(in);
              }

          }
      });

      listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
          @Override
          public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
              Object o = listView.getAdapter().getItem(i);
              if (o instanceof Map) {
                  Map map = (Map) o;
                  deleteBookmark(String.valueOf(map.get(TAG_TITLE)), String.valueOf(map.get(TAG_LINK)));
              }

              return true;
          }
      });

  }

  private class LoadBookmarks extends AsyncTask<String, String, String> {

      @Override
      protected void onPreExecute() {
          super.onPreExecute();
      }

      @Override
      protected String doInBackground(String... args) {
          //updating UI from Background Thread
          runOnUiThread(new Runnable() {
              public void run() {

                  SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
                  String jsonLink = sharedPreferences.getString(WEB_LINKS, null);
                  String jsonTitle = sharedPreferences.getString(WEB_TITLE, null);
                  listRowData = new ArrayList<>();

                  if (jsonLink != null && jsonTitle != null) {

                      Gson gson = new Gson();
                      ArrayList<String> linkArray = gson.fromJson(jsonLink, new TypeToken<ArrayList<String>>() {
                      }.getType());

                      ArrayList<String> titleArray = gson.fromJson(jsonTitle, new TypeToken<ArrayList<String>>() {
                      }.getType());

                      for (int i = 0; i < linkArray.size(); i++) {
                          HashMap<String, String> map = new HashMap<>();

                          if (titleArray.get(i).length() == 0)
                              map.put(TAG_TITLE, "Bookmark " + (i + 1));
                          else
                              map.put(TAG_TITLE, titleArray.get(i));

                          map.put(TAG_LINK, linkArray.get(i));
                          listRowData.add(map);
                      }

                      adapter = new SimpleAdapter(BookmarkActivity.this,
                              listRowData, R.layout.bookmark_list_row,
                              new String[]{TAG_TITLE, TAG_LINK},
                              new int[]{R.id.title, R.id.link});

                      listView.setAdapter(adapter);
                  }

                  linearLayout.setVisibility(View.VISIBLE);
                  listView.setEmptyView(linearLayout);

              }
          });
          return null;
      }

      protected void onPostExecute(String args) {
          mSwipeRefreshLayout.setRefreshing(false);
      }

  }

  private void deleteBookmark(final String title, final String link) {

      new AlertDialog.Builder(this)
              .setTitle("DELETE")
              .setMessage("Confirm that you want to delete this bookmark?")
              .setPositiveButton("YES", new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialogInterface, int i) {
                      SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
                      String jsonLink = sharedPreferences.getString(WEB_LINKS, null);
                      String jsonTitle = sharedPreferences.getString(WEB_TITLE, null);

                      if (jsonLink != null && jsonTitle != null) {

                          Gson gson = new Gson();
                          ArrayList<String> linkArray = gson.fromJson(jsonLink, new TypeToken<ArrayList<String>>() {
                          }.getType());

                          ArrayList<String> titleArray = gson.fromJson(jsonTitle, new TypeToken<ArrayList<String>>() {
                          }.getType());

                          linkArray.remove(link);
                          titleArray.remove(title);

                          SharedPreferences.Editor editor = sharedPreferences.edit();
                          editor.putString(WEB_LINKS, new Gson().toJson(linkArray));
                          editor.putString(WEB_TITLE, new Gson().toJson(titleArray));
                          editor.apply();

                          new LoadBookmarks().execute();
                      }
                      dialogInterface.dismiss();
                  }
              }).setNegativeButton("NO", new DialogInterface.OnClickListener() {
          @Override
          public void onClick(DialogInterface dialogInterface, int i) {
              dialogInterface.dismiss();
          }
      }).show();
  }

}

在上面的代码中,我们使用Gson反序列化了SharedPreferences中的字符串,并将它们转换为AsyncTask LoadBookmarks内部的相应链接和标题ArrayList of Strings。

SimpleAdapter是ListView的内置适配器。
将静态数据映射到XML文件中定义的视图很有用。

ListView行的布局如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  >

  <TextView
      android:id="@+id/title"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:ellipsize="end"
      android:maxLines="1"
      android:paddingBottom="2dp"
      android:paddingTop="4dp"
      android:textColor="#000"
      android:textSize="16sp" 

  <TextView
      android:id="@+id/link"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:ellipsize="end"
      android:maxLines="1"
      android:layout_below="@+id/title"
      android:paddingBottom="4dp"
      android:paddingTop="2dp"
      android:textSize="14sp" 

</RelativeLayout>

调用setOnItemLongClickListener()可以长按删除书签。
返回一个" false"也会同时调用" setOnItemClickListener()",因此建议返回" true"。