# 仿9GAG制作过程(四)

# 有话要说

这次主要讲述主页面下拉刷新和上拉加载功能的实现。

主要是使用了SwipeRefreshLayout的布局方式,并在此基础上通过RecyclerView的特性增加了上拉加载的功能。

# 成果

# 实现方式

# 页面布局

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipeRefreshView"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:background="#f0f0f0"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
1
2
3
4
5
6
7
8
9
10

通过SwipeRefreshLayout来实现下拉刷新功能。


# 下拉刷新

SwipeRefreshLayout swipeRefreshLayout;        
// 下拉刷新控件
swipeRefreshLayout = getView().findViewById(R.id.swipeRefreshView);
// 设置下拉控件背景色
swipeRefreshLayout.setProgressBackgroundColorSchemeColor(Color.WHITE);
// 设置下来控件主色
swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent);
// 设置下拉刷新事件
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
    @Override
    public void onRefresh() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String url = baseUrl + (++currentPage);
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder()
                        .url(url)
                        .build();
                try {
                    Response response = client.newCall(request).execute();
                    String json = response.body().string();
                    if (json != null) {
                        Gson gson = new Gson();
                        List<NewsBean> newDatas = gson.fromJson(json, new TypeToken<List<NewsBean>>(){}.getType());
                        if (newsBeans != null && newsBeans.size() > 0) {
                            newsBeans.addAll(0, newDatas);
                        }
                    }
                    Message message = new Message();
                    message.what = UPDATE_NEWS;
                    handler.sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

通过setProgressBackgroundColorSchemeColor来设置下拉控件的背景色,也就是圈圈的主体颜色。

通过setColorSchemeResources来设置下拉控件中间的线条颜色。

通过setOnRefreshListener来定义下拉刷新事件。

然后通过currentPage来实现下拉刷新之后获取的数据是下一页的数据,再添加到集合开头。

private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case QUERY_NEWS:
                recyclerView.getAdapter().notifyDataSetChanged();
                break;
            case UPDATE_NEWS:
                recyclerView.getAdapter().notifyDataSetChanged();
                swipeRefreshLayout.setRefreshing(false);
                break;
            case LOAD_MORE:
                ((NewsAdapter)recyclerView.getAdapter()).changeStatus(NewsAdapter.UNLOADING);
                currentState = NewsAdapter.UNLOADING;
                break;
        }
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

刷新完成之后,通过notifyDataSetChanged告诉RecyclerView数据改变了,进而更改页面显示。通过setRefreshing来控制下拉刷新控件的显示。

由此,完成了下拉刷新的实现。


# 上拉加载

由于SwipeRefreshLayout并不提供上拉加载的功能,于是准备利用RecyclerView灵活的特性来实现上拉加载功能。

package com.example.lanxingren.imitating9gag.adapter;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.example.lanxingren.imitating9gag.R;
import com.example.lanxingren.imitating9gag.bean.NewsBean;
import com.example.lanxingren.imitating9gag.util.GlideApp;

import java.util.List;

public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<NewsBean> myNewsList;
    private Context myContext;

    // 是否加载
    public static final int LOADING = 1;
    public static final int UNLOADING = 2;

    private int mStatus = UNLOADING;// 当前加载状态

    // item的viewType
    private final int ITEM = 1;
    private final int FOOTER = 2;

    static class NewsHolder extends RecyclerView.ViewHolder {
        CardView cardView;
        TextView titleView;
        ImageView imageView;
        TextView pointView;
        ImageView likeImageView;
        ImageView unlikeImageView;


        private NewsHolder (View view) {
            super(view);
            cardView = (CardView) view;
            titleView = view.findViewById(R.id.item_title);
            imageView = view.findViewById(R.id.item_image);
            pointView = view.findViewById(R.id.item_point);
            likeImageView = view.findViewById(R.id.item_like);
            unlikeImageView = view.findViewById(R.id.item_unlike);
        }
    }

    static class FooterHolder extends RecyclerView.ViewHolder {
        CardView cardView;
        private FooterHolder (View view) {
            super(view);
            cardView = view.findViewById(R.id.cardView_footer);
        }
    }

    public NewsAdapter (List<NewsBean> newsList) {
        this.myNewsList = newsList;
    }

    @Override
    public int getItemCount() {
        int count = 0;
        if (myNewsList != null) {
            count = myNewsList.size() + 1;
        }
        return count;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (myContext == null) {
            myContext = parent.getContext();
        }

        if (viewType == ITEM) {
            View view = LayoutInflater.from(myContext).inflate(R.layout.item_news, parent, false);
            return new NewsHolder(view);
        } else {
            View view = LayoutInflater.from(myContext).inflate(R.layout.item_footer, parent, false);
            return new FooterHolder(view);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof NewsHolder) {
            final NewsHolder newsHolder = (NewsHolder)holder;
            final NewsBean newsBean = myNewsList.get(position);

            // 设置标题
            String title = "9GAG#" + newsBean.getId();
            if (newsBean.getTitle() != null && newsBean.getTitle().length() > 0) {
                title = newsBean.getTitle();
            }
            newsHolder.titleView.setText(title);

            // 屏幕宽度
            int screenWidth = myContext.getResources()
                    .getDisplayMetrics()
                    .widthPixels;
            // 屏幕高度
            int screenHeight = myContext.getResources()
                    .getDisplayMetrics()
                    .heightPixels;

            // 设置图片,不知道为什么override这样设置就可以让图片正正好显示,有时间研究一下?
            if (newsBean.getUrls() != null && newsBean.getUrls().size() > 0) {
                GlideApp.with(myContext)
                        .load(newsBean.getUrls().get(0))
                        .override(screenWidth, screenHeight)
                        .into(newsHolder.imageView);
            }

            // 设置点赞数
            String point = Integer.toString(newsBean.getLike() - newsBean.getUnlike());
            newsHolder.pointView.setText(point);

            // 设置点赞图标显示
            int accentColor = myContext.getResources().getColor(R.color.colorAccent);
            int defaultColor = myContext.getResources().getColor(R.color.defaultColor);
            switch (newsBean.getIsLiked()) {
                case -1:
                    newsHolder.likeImageView.setColorFilter(defaultColor);
                    newsHolder.unlikeImageView.setColorFilter(accentColor);
                    break;
                case 0:
                    newsHolder.likeImageView.setColorFilter(defaultColor);
                    newsHolder.unlikeImageView.setColorFilter(defaultColor);
                    break;
                case 1:
                    newsHolder.likeImageView.setColorFilter(accentColor);
                    newsHolder.unlikeImageView.setColorFilter(defaultColor);
                    break;
            }

            // 初始化监听事件
            newsHolder.likeImageView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    newsBean.setIsLiked(1);
                    notifyDataSetChanged();
                }
            });
            newsHolder.unlikeImageView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    newsBean.setIsLiked(-1);
                    notifyDataSetChanged();
                }
            });

        } else if (holder instanceof FooterHolder) {
            switch (mStatus) {
                case UNLOADING:
                    ((FooterHolder) holder).cardView.setVisibility(View.GONE);
                    break;
                case LOADING:
                    ((FooterHolder) holder).cardView.setVisibility(View.VISIBLE);
                    break;
            }
        }
    }

    @Override
    public int getItemViewType(int position) {
        if (position + 1 == getItemCount()) {
            return FOOTER;
        } else {
            return ITEM;
        }
    }

    public void changeStatus(int status) {
        this.mStatus = status;
        notifyDataSetChanged();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

在适配器中定义了两个布局,一个是普通布局,一个是尾布局(footer)。

下面是footer的具体布局:

<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_marginTop="10dp"
    android:layout_marginBottom="0dp"
    app:cardCornerRadius="0dp"
    android:elevation="0dp"
    android:id="@+id/cardView_footer">
    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_gravity="center_horizontal"
        />
</android.support.v7.widget.CardView>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 注意点

  • 通过mStatus来判断尾布局是否展示
  • ITEM代表的是普通布局,即段子的布局;FOOTER代表的是尾布局
  • 由于增加了一个item,故getItemCount得在原先的基础上加上一
  • onCreateViewHolder中通过viewType来创建不同的viewHolder
  • 实现getItemViewType方法,根据position的值来确定viewType
  • onBindViewHolder中先判断viewHolder的类型,如果是尾布局(footer)的话,再根据mStatus来判断是否展示
  • changeStatus主要是给外面用的,通过该方法可以控制footer的显示

# 使用上拉加载

private void initLoadMoreListener() {
    recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(final RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);

            // 获取当前可见的item位置
            int lastVisiblePosition = 0;
            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager instanceof LinearLayoutManager) {
                lastVisiblePosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
            }

            // 当前加载状态是UNLOADING && 当前可见的item位置是最后一条时
            if (currentState == NewsAdapter.UNLOADING
                    && lastVisiblePosition + 1 == recyclerView.getAdapter().getItemCount()) {
                // 改变footer的可见性
                ((NewsAdapter)recyclerView.getAdapter()).changeStatus(NewsAdapter.LOADING);
                currentState = NewsAdapter.LOADING;

                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        String url = baseUrl + (++currentPage);
                        OkHttpClient client = new OkHttpClient();
                        Request request = new Request.Builder()
                                .url(url)
                                .build();
                        try {
                            Response response = client.newCall(request).execute();
                            String json = response.body().string();
                            if (json != null) {
                                Gson gson = new Gson();
                                List<NewsBean> newDatas = gson.fromJson(json, new TypeToken<List<NewsBean>>(){}.getType());
                                if (newsBeans != null && newsBeans.size() > 0) {
                                    newsBeans.addAll(newsBeans.size(), newDatas);
                                }
                            }
                            Message message = new Message();
                            message.what = LOAD_MORE;
                            handler.sendMessage(message);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

通过给recyclerView加上滚动事件来实现下拉加载功能,具体逻辑如下:

  1. 获取页面可见最下面的item位置
  2. 判断当前尾布局的加载状态
  3. 如果尾布局现在的状态是UNLOADING && item位置为最后一个,则开线程加载数据
  4. 将尾布局展示,即开始转圈动画
  5. 请求网络,获取数据,放入数据集
  6. 根据handler的处理,告诉RecyclerView数据改变,然后将footer隐藏

通过以上的过程就可以实现上拉加载的效果。

# 参考

# 结束语

本次学习了Android的下拉刷新以及上拉加载的实现,对RecyclerView有了进一步的了解。

接下来准备实现点赞功能以及GIF的暂停功能。

更新时间: 10/20/2022, 7:04:01 AM