美文网首页Android开发经验谈
Android基础回顾(二)| 常用控件 — ListView和

Android基础回顾(二)| 常用控件 — ListView和

作者: CCCode1997 | 来源:发表于2018-10-30 21:47 被阅读12次

    参考书籍:《第一行代码》 第二版 郭霖
    如有错漏,请批评指出!

    关于基本控件以及常用布局的使用,这里不作赘述,多写写布局就能掌握技巧。下面我们直接讨论ListView和RecyclerView的使用。

    ListView

    这里我想通过实现一个功能来了解ListView的使用:访问鸿洋提供的开放API 玩Android 获取首页文章,并通过ListView展示出来。

    1. 首先,我们创建一个ListViewActivity,实现它的布局。

      <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <ListView
              android:id="@+id/list_view"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>
      
          <ImageView
              android:id="@+id/empty_view_list"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:src="@drawable/img_empty_data"
              tools:ignore="ContentDescription" />
      
      </FrameLayout>
      

      添加ListView控件很简单,只需要让它充满父布局,并为其指定一个id即可,至于下面的ImageView我们暂且不管它,后面再来说它的功能。

    2. 接下来我们来定义一个实体类,在此之前,我们先看看这个接口中提供的数据有哪些,打开http://www.wanandroid.com/article/list/0/json

      这些就是这个接口返回的JSON数据,我们可以通过一个在线解析工具:JSON在线视图查看器来查看。

      我们只需要将JSON数据全部复制到这个网站的JSON数据里面,然后点击左边的视图按钮,就可以看到这些JSON数据的结构了。

      现在我们来决定我们需要展示什么,简单起见,我们就展示title、author、niceDate这些字段,不过还有一个link字段我们也需要获取到,也就是这篇文章的链接。所以我们需要定义这样一个Article实体类:

      public class Article {
          private String title;
          private String author;
          private String date;
          private String link;
      
          public Article(){}
      
          public Article(String title, String author, String date, String link){
              this.title = title;
              this.author = author;
              this.date = date;
              this.link = link;
          }
      
          public String getTitle() {
              return title;
          }
      
          public void setTitle(String title) {
              this.title = title;
          }
      
          public String getAuthor() {
              return author;
          }
      
          public void setAuthor(String author) {
              this.author = author;
          }
      
          public String getDate() {
              return date;
          }
      
          public void setDate(String date) {
              this.date = date;
          }
      
          public String getLink() {
              return link;
          }
      
          public void setLink(String link) {
              this.link = link;
          }
      }
      
    3. 下一步,我们要为ListView的item创建一个自定义布局,也就是我们要如何显示刚刚我们想要展示的内容。在layout目录下创建一个item_article.xml布局文件。

      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:padding="12dp"
          android:paddingTop="24dp"
          android:paddingBottom="24dp">
      
          <ImageView
              android:id="@+id/img_article"
              android:src="@drawable/article"
              android:layout_width="36dp"
              android:layout_height="36dp"
              android:layout_marginRight="12dp"
              android:layout_marginEnd="12sp"
              android:layout_centerVertical="true"
              tools:ignore="ContentDescription" />
      
          <TextView
              android:id="@+id/text_title"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:textSize="18sp"
              android:textColor="@color/colorBlack"
              android:layout_toRightOf="@id/img_article"
              android:layout_toEndOf="@id/img_article"
              android:layout_marginBottom="10dp"
              android:singleLine="true"
              android:ellipsize="end" />
      
          <TextView
              android:id="@+id/text_inform"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="@string/text_inform"
              android:textSize="12sp"
              android:layout_toRightOf="@id/img_article"
              android:layout_toEndOf="@id/img_article"
              android:layout_below="@id/text_title"
              android:layout_marginRight="20dp"
              android:layout_marginEnd="20dp"
              android:singleLine="true"
              android:ellipsize="end"/>
      
      </RelativeLayout>
      
      它的显示效果就是这样的:
    4. 然后,我们需要创建一个自定义适配器,用来将数据与我们的自定义item中的控件绑定起来,这样就能保证数据正确显示了。下面看代码:

      public class ListAdapter extends BaseAdapter {
      
          private List<Article> mArticles;
          private LayoutInflater mInflater;
      
          public ListAdapter(Context context, List<Article> articles){
              this.mArticles = articles;
              this.mInflater = LayoutInflater.from(context);
          }
      
          @Override
          public int getCount() {
              return mArticles.size();
          }
      
          @Override
          public Object getItem(int position) {
              return mArticles.get(position);
          }
      
          @Override
          public long getItemId(int position) {
              return position;
          }
      
          @Override
          public View getView(int position, View convertView, ViewGroup parent) {
              ViewHolder holder;
              if (convertView == null){
                  holder = new ViewHolder();
                  convertView = mInflater.inflate(R.layout.item_article, parent, false);
                  holder.mTitleText = (TextView)convertView.findViewById(R.id.text_title);
                  holder.mInformText = (TextView)convertView.findViewById(R.id.text_inform);
                  convertView.setTag(holder);
              }else {
                  holder = (ViewHolder)convertView.getTag();
              }
              Article mArticle = mArticles.get(position);
              holder.mTitleText.setText(mArticle.getTitle());
              String format = parent.getResources().getString(R.string.text_inform);
              holder.mInformText.setText(String.format(format, mArticle.getAuthor(), mArticle.getDate()));
              return convertView;
          }
      
          public final class ViewHolder{
              TextView mTitleText;
              TextView mInformText;
          }
      }
      

      BaseAdapter是一个抽象类,让我们的ListViewAdapter继承它,重写它的getCount()、getItem()、getItenid()以及getView()方法。另外,我们在其内部定义了一个内部类ViewHolder。这种实现方式充分利用了ListView的试图缓存机制,避免每次在调用getView()方法时都要通过findviewByid()实例化控件,当convertView为null时,使用LayoutInflater加载布局,并创建一个ViewHolder对象,并将控件的实例存放在ViewHolder中,这样就不需要每次都实例化控件,可以大大提高ListView的运行效率。

    5. 最后就是在ListViewActivity中获取数据并将数据传递给ListView显示出来。

      package com.example.laughter.aboutui.activity;
      
      import android.support.annotation.NonNull;
      import android.support.v7.app.AppCompatActivity;
      import android.os.Bundle;
      import android.widget.ListView;
      
      import com.example.laughter.aboutui.R;
      import com.example.laughter.aboutui.adapter.ListAdapter;
      import com.example.laughter.aboutui.model.Article;
      import com.example.laughter.aboutui.util.HttpUtil;
      
      import org.json.JSONArray;
      import org.json.JSONException;
      import org.json.JSONObject;
      
      import java.io.IOException;
      import java.util.ArrayList;
      import java.util.List;
      
      import okhttp3.Call;
      import okhttp3.Callback;
      import okhttp3.Response;
      
      public class ListViewActivity extends AppCompatActivity {
      
          private List<Article> mArticles;
          private ListAdapter adapter;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_list_view);
              initView();
              initData();
          }
      
          private void initView(){
              setTitle("ListViewActivity");
              ListView listView = (ListView)findViewById(R.id.list_view);
              listView.setEmptyView(findViewById(R.id.empty_view_list));
              mArticles = new ArrayList<>();
              adapter = new ListAdapter(this, mArticles);
              listView.setAdapter(adapter);
          }
      
          private void initData(){
              String address = "http://www.wanandroid.com/article/list/0/json";
              HttpUtil.sendOkHttpRequest(address, new Callback() {
                  @Override
                  public void onFailure(@NonNull Call call, @NonNull IOException e) {
      
                  }
                  @Override
                  public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                      if (response.body() != null) {
                          parseJSONWithJSONObject(response.body().string());
                          showArticles();
                      }
                  }
              });
          }
      
          private void parseJSONWithJSONObject(String jsonData){
              try {
                  JSONObject json = new JSONObject(jsonData);
                  JSONObject data = json.getJSONObject("data");
                  JSONArray datas = data.getJSONArray("datas");
                  for (int i=0;i<datas.length();i++){
                      JSONObject jsonObject = datas.getJSONObject(i);
                      Article mArticle = new Article();
                      mArticle.setAuthor(jsonObject.getString("author"));
                      mArticle.setDate(jsonObject.getString("niceDate"));
                      mArticle.setTitle(jsonObject.getString("title"));
                      mArticle.setLink(jsonObject.getString("link"));
                      mArticles.add(mArticle);
                  }
              } catch (JSONException e) {
                  e.printStackTrace();
              }
          }
      
          private void showArticles(){
              runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                      adapter.notifyDataSetChanged();
                  }
              });
          }
      }
      
      首先我们在initView()方法中对控件进行初始化,这里我们调用了ListView的setemptyView()方法,这个方法是为了设置一个空数据情况下的默认提示,即当我们的数据为空时,会显示这个ImageView,目的在于优化用户体验,不至于显示一片空白。然后初始化Article数组,并实例化ListAdapter对象,通过ListView的setAdapter()方法将adapter传递进去,这时Article数组无数据。接下来在initData()方法中调用我们封装好的HttpUtil类中的静态方法sendOkHttpRequest()发送HTTP请求,然后调用parseJSONWithJSONObject()方法解析JSON数据,将数据添加到Article数组中,最后调用showArticles()方法,调用runOnUiThread方法切回主线程,在主线程中调用ListAdapter的notifyDataSetChanged()方法更新adapter中的数据,这样就实现了我们的ListView。当然,我们这里仅仅显示了一部分数据,接口中提供的数据是分页的,我们通过更改url中的页码(list和json中间的数字即为页码)可以获取到每一页的数据,这一部分功能我就不去实现了,感兴趣的话可以自己动动手。中间关于发送HTTP请求获取数据以及解析JSON数据的部分,我们会在后续章节了解到。下面看效果:

      到这里,我们的ListView显示出了我们获取到的数据。但是还没有完,我们还需要给每一个item设置一个点击事件,用来显示我们获取到的link中的文章内容。

    6. 添加详情页
      首先我们要创建一个WebActivity,因为我们的文章内容其实是一个网页,因此我们可以使用WebView来展示,所以在WebActivity的布局中我们需要添加一个WebView。

      <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <WebView
              android:id="@+id/web_view"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>
      
      </FrameLayout>
      

      接下来我们要给ListView设置点击事件,每点击一个item,就将该item对应文章的link值传递给WebActivity,并且启动WebActivity。我在 Android基础回顾(二)| 关于Activity 这篇文章里面介绍过Activity之间传递数据的方法,这里就不赘述了。这里只需要修改initView()方法,直接看代码:

      private void initView(){
          setTitle("ListViewActivity");
          ListView listView = (ListView)findViewById(R.id.list_view);
          listView.setEmptyView(findViewById(R.id.empty_view_list));
          listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
              @Override
              public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                  String link = mArticles.get(position).getLink();
                  String title = mArticles.get(position).getTitle();
                  Intent intent = new Intent(ListViewActivity.this, WebActivity.class);
                  intent.putExtra("link", link);
                  intent.putExtra("title", title);
                  startActivity(intent);
              }
          });
          mArticles = new ArrayList<>();
          adapter = new ListAdapter(this, mArticles);
          listView.setAdapter(adapter);
      }
      

      其实给ListView添加点击事件很简单,就是使用setOnItemClickListener()方法为ListView注册一个监听器,当用户点击ListView的任何一个子项时,就会回调onItemClick()方法,在这个方法中,可以通过position参数获取到对应子项的数据,然后通过Intent传递给WebActivity。接下来就是处理WebActivity中的逻辑了。

      public class WebActivity extends AppCompatActivity {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_web);
              initData();
          }
      
          private void initData(){
              Intent intent = getIntent();
              if (intent != null){
                  String address = intent.getStringExtra("link");
                  String title = intent.getStringExtra("title");
                  setTitle(title);
                  WebView mWebView = (WebView)findViewById(R.id.web_view);
                  mWebView.setWebViewClient(new WebViewClient());
                  mWebView.loadUrl(address);
              }
          }
      }
      
      我们获取到intent传递过来的数据,然后创建一个WebView对象,调用它的setWebViewClient()方法,并传入一个WebViewClient实例。它的作用是,当我们从一个网页跳到另一个网页时,我们希望目标网页仍然在当前WebView中显示,而不是打开系统浏览器。最后调用loadUrl()方法,加载页面。这样,我们的功能就完成了。当然,这只是单纯的实现功能,其实还有很多地方需要完善,比如给WebView添加加载动画等。感兴趣的话可以自己去探索实现。我们再来看一下效果吧!

    RecyclerView

    简单起见,我们使用RecyclerView实现一个和上面相同的功能。

    1. 首先,创建一个RecyclerViewActivity,并实现它的布局。
      这里我们需要知道,RecyclerView属于新增控件,它被定义在support库中,因此,我们需要在项目的build.gradle中添加相应的依赖库。打开app/build.gradle文件,在dependencies闭包中添加下面的代码:
      implementation 'com.android.support:recyclerview-v7:28.0.0'
      
      这个文件修改之后都会自动提示Sync Now,点击来进行同步。
      接下来就可以使用了,由于RecyclerView不是内置在系统SDK中的,所以需要把完整的包路径写出来(不过我们只需敲出RecyclerView,Android Studio会自动帮我们补全)。
      <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
      
          <android.support.v7.widget.RecyclerView
              android:id="@+id/recycler_view"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>
      
      </FrameLayout>
      
    2. 由于前面我们实现ListView时已经定义过Article实体类和item_article.xml布局文件了,这里我们直接使用就行了。
    3. 和ListView一样,RecyclerView也需要一个适配器。我们创建一个Java Class,命名为RecyclerAdapter。具体实现如下:
      package com.example.laughter.aboutui.adapter;
      
      import android.content.Context;
      import android.support.annotation.NonNull;
      import android.support.v7.widget.RecyclerView;
      import android.view.LayoutInflater;
      import android.view.View;
      import android.view.ViewGroup;
      import android.widget.TextView;
      
      import com.example.laughter.aboutui.R;
      import com.example.laughter.aboutui.model.Article;
      
      import java.util.List;
      
      public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {
      
          private List<Article> mArticles;
          private LayoutInflater mInflater;
          private ViewGroup mViewGroup;
      
          public RecyclerAdapter(Context context, List<Article> articles){
              mArticles = articles;
              mInflater = LayoutInflater.from(context);
          }
      
          @NonNull
          @Override
          public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) {
              View view = mInflater.inflate(R.layout.item_article, parent, false);
              mViewGroup = parent;
              return new ViewHolder(view);
          }
      
          @Override
          public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
              Article mArticle = mArticles.get(position);
              holder.title.setText(mArticle.getTitle());
              String format = mViewGroup.getResources().getString(R.string.text_inform);
              holder.inform.setText(String.format(format, mArticle.getAuthor(), mArticle.getDate()));
          }
      
          @Override
          public int getItemCount() {
              return mArticles.size();
          }
      
          static class ViewHolder extends RecyclerView.ViewHolder{
              TextView title;
              TextView inform;
      
              ViewHolder(View itemView) {
                  super(itemView);
                  title = (TextView)itemView.findViewById(R.id.text_title);
                  inform = (TextView)itemView.findViewById(R.id.text_inform);
              }
          }
      }
      
      我们先为其定义一个静态内部类ViewHolder,让它继承自RecyclerView.ViewHolder,对应的,定义我们所需要的两个TextView控件,然后重写它的构造方法,并实例化我们定义的控件。然后让我们的RecyclerAdapter继承RecyclerView.Adapter,并将泛型指定为我们刚才定义的RecyclerAdapter.ViewHolder。接着,我们需要重写onCreateViewHolder()、onBindViewHolder() 和 getItemCount() 这三个方法。onCreateViewHolder()方法用于创建ViewHolder实例,我们使用LayoutInflater的inflate()方法来加载item_articlle布局,然后创建一个ViewHolder对象并返回。onBindViewHolder()方法用于为每个item加载数据,在每个item滚动到屏幕内时会被调用,这里我们通过传入的position参数得到当前item对应的Article实例,然后通过传入的ViewHolder对象获取到当前item的子控件实例,对其进行操作即可。
    4. 接下来我们需要做的就是获取数据并传递给RecyclerView显示出来。下面看RecyclerViewActivity的代码:
      package com.example.laughter.aboutui.activity;
      
      import android.support.annotation.NonNull;
      import android.support.v7.app.AppCompatActivity;
      import android.os.Bundle;
      import android.support.v7.widget.LinearLayoutManager;
      import android.support.v7.widget.RecyclerView;
      
      import com.example.laughter.aboutui.R;
      import com.example.laughter.aboutui.adapter.RecyclerAdapter;
      import com.example.laughter.aboutui.model.Article;
      import com.example.laughter.aboutui.util.HttpUtil;
      
      import org.json.JSONArray;
      import org.json.JSONException;
      import org.json.JSONObject;
      
      import java.io.IOException;
      import java.util.ArrayList;
      import java.util.List;
      
      import okhttp3.Call;
      import okhttp3.Callback;
      import okhttp3.Response;
      
      public class RecyclerViewActivity extends AppCompatActivity {
      
          private List<Article> mArticles;
          private RecyclerView recyclerView;
          private RecyclerAdapter adapter;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_recycler_view);
              initView();
              initData();
          }
      
          private void initView(){
              recyclerView = (RecyclerView)findViewById(R.id.recycler_view);
              LinearLayoutManager layoutManager = new LinearLayoutManager(this);
              recyclerView.setLayoutManager(layoutManager);
              mArticles = new ArrayList<>();
              adapter = new RecyclerAdapter(this, mArticles);
              recyclerView.setAdapter(adapter);
          }
      
          private void initData(){
              String address = "http://www.wanandroid.com/article/list/0/json";
              HttpUtil.sendOkHttpRequest(address, new Callback() {
                  @Override
                  public void onFailure(@NonNull Call call, @NonNull IOException e) {
      
                  }
                  @Override
                  public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                      if (response.body() != null) {
                          parseJSONWithJSONObject(response.body().string());
                          showArticles();
                      }
                  }
              });
          }
      
          private void parseJSONWithJSONObject(String jsonData){
              try {
                  JSONObject json = new JSONObject(jsonData);
                  JSONObject data = json.getJSONObject("data");
                  JSONArray mJsonArray = data.getJSONArray("datas");
                  for (int i=0;i<mJsonArray.length();i++){
                      JSONObject mJSONObject = mJsonArray.getJSONObject(i);
                      String title = mJSONObject.getString("title");
                      String author = mJSONObject.getString("author");
                      String date = mJSONObject.getString("niceDate");
                      String link = mJSONObject.getString("link");
                      Article mArticle = new Article(title, author, date, link);
                      mArticles.add(mArticle);
                  }
              } catch (JSONException e) {
                  e.printStackTrace();
              }
          }
      
          private void showArticles(){
              runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                      adapter.notifyDataSetChanged();
                  }
              });
          }
      }
      
      获取数据的逻辑是一样的,就不重复了。我们主要看ininView()方法,首先实例化RecyclerView对象,然后创建一个LinearLayoutManager对象,调用RecyclerView的setLayoutManager方法设置布局管理器,将LinearlayoutManager对象传递进去,使用线性布局的方式管理RecyclerView的item,然后初始化Article数组,实例化RecyclerAdapter对象,设置为RecyclerView的适配器,最后在数据更新后调用notifyDataSetChanged()方法更新数据。这样RecyclerView的基本功能就实现了。

    上一篇:Android基础回顾(一)| 关于Activity
    下一篇:Android基础回顾(三)| 关于Fragment


    相关文章

      网友评论

        本文标题:Android基础回顾(二)| 常用控件 — ListView和

        本文链接:https://www.haomeiwen.com/subject/ztoptqtx.html