高仿知乎日报(三)[主页面上拉刷新]

主页面上拉刷新实现

by seaicelin
转载请注明出处:http://blog.csdn.net/amd123456789/article/details/52331481

接着上一篇的问题:

  • RecylerView 如何上拉加载更多条目?

我们来理一下这个思路吧,看看需要我们来实现什么?

  1. UI上需要在RecylerView列表上添加ScrollListener,增加上拉的逻辑判断,并添加实现?
  2. RecylerView滑动的过程中,ToolBar的Title文本需要更新为当天日报条目对应的日期?
  3. RecylerView需要对两种条目进行处理,显示日期和当前日期日报条目这两种Item?
  4. 下拉过程中,对前一天条目Json数据的获取和保存怎么实现?

以上就是我遇到的问题,一个一个解决吧!

1. 上拉逻辑实现

首先,上拉的过程中,需要到最后一个条目才会去加载。因此我们需要判断此时RecylerView可见的最后一个条目位置,是不是刚好等于总的条目数。

@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    //super.onScrollStateChanged(recyclerView, newState);
    LogUtils.e("onScrollStateChanged");
    RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
    int visibleCount = layoutManager.getChildCount();
    int totalCount = layoutManager.getItemCount();

    // 四个条件,分别是
    // 1. 是否有数据,
    // 2. 状态是否是滑动停止状态,
    // 3. 显示的最大条目是否大于整个数据(注意偏移量),
    // 4. 是否正在加载数据
    if (visibleCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE
            && lastVisiableItemPos >= totalCount - 1 && !isLoadingData) {
        if (listener != null) {
            isLoadingData = true;
            listener.loadMore();
        }
    }
}

2. ToolBar Title的实时更新

针对这个问题,无非就是在Recylerview滑动的过程中,实时更新它。问题的关键其实是怎么获取到这个Item所对应的日期。很简单,我们在HomeStoriesInfo这个类中,添加两个成员,
1. 一个是存储当天条目的所对应的日期
2. 一个是当前条目是否是显示日期标志位。
这样一来,增加这两个成员变量就同时解决了第三个问题:

  • RecylerView需要对两种条目进行处理,显示日期和当前日期日报条目这两种Item?

通过对成员变量的判断进行区分,同时HomeStoriesInfoList可以存储这个日期,就不用在另外做区分对待。

public class HomeStoriesInfo {
    ...
    private boolean isDateTime;//是否是日期条目,是就用来对应DateHolder
    private String date;//存储当前条目是那个日期,Toolbar更新Title就是用这个
    ...
}

好了,知道怎么解决这几个问题,咱就先来把这三个UI的问题给解决了。实际上就是对RecylerView的OnScrollListener进行修改,把这个类重新来实现:

package com.example.seaice.zhihuribao.view;

import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import com.example.seaice.zhihuribao.Utils.LogUtils;
/**
 * Created by seaicelin on 2016/8/25.
 */
public class LoadDataScrollController extends RecyclerView.OnScrollListener implements SwipeRefreshLayout.OnRefreshListener {

public static final int LINEAR_LAYOUT = 0;
public static final int GRID_LAYOUT = 1;
public static final int STRAGGERED_GRID_LAYOUT = 2;

private int layoutType = 0;
private int lastVisiableItemPos;//最后可见条目位置
private int firstVisibleItemPos;//第一条可见条目位置
private int[] lastPos;
private boolean isLoadingData = false;//是否正在加载数据
private OnRecycleRefreshListener listener;//外部需要实现的接口

public LoadDataScrollController(OnRecycleRefreshListener listener) {
    this.listener = listener;
}

@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    LogUtils.e("onScrollStateChanged");
    RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
    int visibleCount = layoutManager.getChildCount();
    int totalCount = layoutManager.getItemCount();

    LogUtils.e("visibleCount = " + visibleCount);
    LogUtils.e("totalCount = " + totalCount);
    LogUtils.e("lastVisiableItemPos = " + lastVisiableItemPos);
    LogUtils.e("newState = " + newState);
    LogUtils.e("isLoadingData = " + isLoadingData);

    // 四个条件,分别是
    // 1. 是否有数据,
    // 2. 状态是否是滑动停止状态,
    // 3. 显示的最大条目是否大于整个数据(注意偏移量),
    // 4. 是否正在加载数据
    if (visibleCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE
            && lastVisiableItemPos >= totalCount - 1 && !isLoadingData) {
        if (listener != null) {
            isLoadingData = true;
            listener.loadMore();
        }
    }
}

public void setLoadingDataStatus(boolean isLoadData) {
    this.isLoadingData = isLoadData;
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    //super.onScrolled(recyclerView, dx, dy);
    LogUtils.e("onScrolled");
    RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
    if (layoutManager instanceof LinearLayoutManager) {
        layoutType = LINEAR_LAYOUT;
    } else if (layoutManager instanceof GridLayoutManager) {
        layoutType = GRID_LAYOUT;
    } else if (layoutManager instanceof StaggeredGridLayoutManager) {
        layoutType = STRAGGERED_GRID_LAYOUT;
    }

    switch (layoutType) {
        case LINEAR_LAYOUT:
            lastVisiableItemPos = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
            firstVisibleItemPos = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
            break;
        case GRID_LAYOUT:
            lastVisiableItemPos = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
            firstVisibleItemPos = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
            break;
        case STRAGGERED_GRID_LAYOUT:
            StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
            if (lastPos == null) {
                lastPos = new int[staggeredGridLayoutManager.getSpanCount()];
            }
            staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(lastPos);
            lastVisiableItemPos = findMax(lastPos);
            firstVisibleItemPos = 1;
            break;
    }
    //更新toolbar title
    if (listener != null) {
        listener.updateTitle(firstVisibleItemPos);
    }
}

private int findMax(int[] lastPos) {
    int max = lastPos[0];
    for (int value : lastPos) {
        if (value > max) {
            max = value;
        }
    }
    return max;
}

//接口
public interface OnRecycleRefreshListener {
    void refresh();
    void loadMore();
    void updateTitle(int pos);
}

@Override
public void onRefresh() {
    if (listener != null) {
        isLoadingData = true;
        listener.refresh();
    }
}
}

可以看到,这里我们把OnRecyclerRefreshListener都交给外部实现,这里只是暴露几个方法。ContentActivity来实现这个接口,具体UI都是在这里实现:

//下拉刷新
@Override
public void refresh() {
    BaseApplication.getHandler().postDelayed(new Runnable() {
        @Override
        public void run() {
            swipeRefreshLayout.setRefreshing(false);
            Toast.makeText(BaseApplication.getApplication(), "刷新成功", Toast.LENGTH_SHORT).show();
        }
    }, 3000);
}

//加载更多
@Override
public void loadMore() {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            OldNewsProtocol protocol = new OldNewsProtocol();
            final List<HomeStoriesInfo> homeInfo = protocol.loadData();
            if (homeInfo != null) {
                UiUtils.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        loadDataCtl.setLoadingDataStatus(false);
                        recylerViewAdapter.add(homeInfo);
                    }
                });
            }
        }
    };
    ThreadMgr.getThreadPool().execute(runnable);
}

@Override
public void updateTitle(int pos) {
    if (pos == 0) {
        toolbar.setTitle(UiUtils.getString(R.string.home_page));
    } else {
        toolbar.setTitle(recylerViewAdapter.getTitle(pos-1));
    }
}

上拉刷新数据的获取和保存

之前主页面和引导页面分别用HomeProtocol和GuideProtocol来加载,这次我们同样生成一个OldNewsProtocol用来加载以前的数据。并且这次数据并不包含HomeTopInfo相关,只有HomeStoriesInfo对应的json数据。

package com.example.seaice.zhihuribao.protocol;

import android.text.format.DateFormat;
import android.text.format.Time;
import com.example.seaice.zhihuribao.Utils.LogUtils;
import com.example.seaice.zhihuribao.Utils.UiUtils;
import com.example.seaice.zhihuribao.bean.HomeStoriesInfo;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/**
 * Created by seaicelin on 2016/8/26.
 */
public class OldNewsProtocol extends BaseProtocol<List<HomeStoriesInfo>> {

private String HOMEURL = "http://news-at.zhihu.com/api/4/news/before/";
private String HOMECACHE;

private static int oldNewsDateCount = 0;//没加载一次增加,表示加载了之前的多少天了

public OldNewsProtocol() {
    //1. 初始化加载的是哪一天
    //2. 初始化HOMECACHE,即保存json的本地文件路径
    initDateToGetNews();
}

@Override
protected List<HomeStoriesInfo> paserJsonData(String json) {
    LogUtils.e("HomeProtocol paserJsonData");
    try {
        JSONObject jsonObject = new JSONObject(json);
        String date = jsonObject.getString("date");
        JSONArray jsonArrayStories = jsonObject.getJSONArray("stories");
        List<HomeStoriesInfo> homeStoriesInfos = new ArrayList<>();
        HomeStoriesInfo dateInfo = new HomeStoriesInfo();
        dateInfo.setDate(formatJsonDate(date));//格式化日期
        dateInfo.setIsDateTime(true);
        homeStoriesInfos.add(dateInfo);
        for (int i = 0; i < jsonArrayStories.length(); i++) {
            JSONObject object = jsonArrayStories.getJSONObject(i);
            int type = object.getInt("type");
            String id = object.getString("id");
            String ga_prefix = object.getString("ga_prefix");
            String title = object.getString("title");
            String images = object.getString("images");
            String image = images.substring(2, images.length() - 2);
            image = image.replace("\\", "");//去掉这个斜杠才能得到数据,take me a lot of time
            HomeStoriesInfo homeStoriesInfo = new HomeStoriesInfo(image, type, id, ga_prefix, title);
            homeStoriesInfo.setDate(formatJsonDate(date));
            homeStoriesInfo.setIsDateTime(false);
            LogUtils.e(homeStoriesInfo.toString());
            homeStoriesInfos.add(homeStoriesInfo);
        }
        return homeStoriesInfos;
    } catch (JSONException e) {
        e.printStackTrace();
    }
    return null;
}

@Override
protected String getCacheDir() {
    return HOMECACHE;
}

@Override
protected String getUrl() {
    return HOMEURL;
}

//下拉加载日报的日期
private void initDateToGetNews() {
    Calendar calendar = Calendar.getInstance();
    Time time = new Time();
    time.setToNow();
    calendar.setTimeInMillis(time.toMillis(false) - 24 * 60 * 60 * 1000 * oldNewsDateCount);
    DateFormat dateFormat = new DateFormat();
    oldNewsDateCount++;
    String date = dateFormat.format("yyyyMMdd", calendar.getTimeInMillis()).toString();
    HOMEURL += date;//对应加载哪天的数据
    HOMECACHE += date;//保存以日期作为文件路径
}
//格式化日期,保存为xx年xx月xx日 星期几
private String formatJsonDate(String date) {
    String sb = "";
    int year = Integer.valueOf(date.substring(0, 4));
    int month = Integer.valueOf(date.substring(4, 6));
    int day = Integer.valueOf(date.substring(6));
    sb += (year + "年" + month + "月" + day + "日");
    Calendar calendar = Calendar.getInstance();
    calendar.set(year, month - 1, day);
    String dayOfWeek = UiUtils.getDayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
    sb += (" " + "星期" + dayOfWeek);
    return sb.toString();
}
}

恩,以上基本就完成了加载数据的功能了,再来贴一下RecylerView Adapter的代码,基本上就明白了,这几个问题就这么解决了:

package com.example.seaice.zhihuribao.adapter;

import android.content.Context;
import android.support.v4.view.ViewPager;
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.seaice.zhihuribao.R;
import com.example.seaice.zhihuribao.Utils.BaseApplication;
import com.example.seaice.zhihuribao.Utils.LogUtils;
import com.example.seaice.zhihuribao.bean.HomeInfo;
import com.example.seaice.zhihuribao.bean.HomeStoriesInfo;
import com.example.seaice.zhihuribao.bean.HomeTopInfo;
import com.example.seaice.zhihuribao.view.CircleIndicator;
import com.squareup.picasso.Picasso;
import java.util.List;
import butterknife.ButterKnife;

/**
 * Created by seaice on 2016/8/22.
 */
public class HomeRecylerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private static final int TYPE_DATE = 2;

private LayoutInflater mInflate;
private Context mContext;
private List<HomeStoriesInfo> homeStoriesInfos;
private List<HomeTopInfo> homeTopInfos;
private List<HomeInfo> homeInfoList;

private boolean isTouchViewPager = false;
private boolean isViewPagerLoop = false;

public HomeRecylerViewAdapter(List<HomeInfo> homeInfo) {
    this.homeInfoList = homeInfo;
    initData();
}

private void initData() {
    HomeInfo homeInfo = homeInfoList.get(0);
    homeStoriesInfos = homeInfo.getHomeStoriesInfos();
    homeTopInfos = homeInfo.getHomeTopInfoList();
    mContext = BaseApplication.getApplication();
    mInflate = LayoutInflater.from(mContext);
    LogUtils.e(homeStoriesInfos.toString());
}

//绑定不同类型Holder
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == TYPE_HEADER) {//view pager
        View view = mInflate.inflate(R.layout.header_recyler_view, parent, false);
        return new HeaderHolder(view);
    } else if (viewType == TYPE_DATE) {//显示日期
        View view = mInflate.inflate(R.layout.date_recyler_view, parent, false);
        return new DateHolder(view);
    } else if (viewType == TYPE_ITEM) {//显示具体条目
        View view = mInflate.inflate(R.layout.item_recyler_view, parent, false);
        return new MyViewHolder(view);
    }
    throw new RuntimeException("there is no type that match the type " + viewType);
}

//设置holder的view元素
@Override
public void onBindViewHolder(RecyclerView.ViewHolder vh, int position) {
    if (vh instanceof HeaderHolder) {
        if (isViewPagerLoop == false) {
            isViewPagerLoop = true;
            setViewPager((HeaderHolder) vh);
        }
    } else if (vh instanceof DateHolder) {
        HomeStoriesInfo homeStoriesInfo = getHomeStoriesListItem(position);
        DateHolder holder = (DateHolder) vh;
        if (position != 1) {
            holder.tv_date.setText(homeStoriesInfo.getDate());
        } else {
            holder.tv_date.setText("今日热闻");
        }
    } else if (vh instanceof MyViewHolder) {
        HomeStoriesInfo info = getHomeStoriesListItem(position);
        MyViewHolder holder = (MyViewHolder) vh;
        holder.tv_title.setText(info.getTitle());
        Picasso.with(BaseApplication.getApplication()).load(info.getImages()).into(holder.iv_title);
    } else {
        throw new RuntimeException("there is no holer that match the  " + vh);
    }
}

@Override
public int getItemCount() {
    int itemCount = homeStoriesInfos.size() + 1;
    LogUtils.e("getItemCount = " + itemCount);
    return itemCount;
}

@Override
public int getItemViewType(int position) {
    if (isPositionHeader(position)) {
        return TYPE_HEADER;
    } else if (isPositionDate(position)) {
        return TYPE_DATE;
    } else {
        return TYPE_ITEM;
    }
}

//判断是否是日期条目位置
private boolean isPositionDate(int position) {
    HomeStoriesInfo homeStoriesInfo = getHomeStoriesListItem(position);
    if (homeStoriesInfo.isDateTime()) {
        return true;
    } else {
        return false;
    }
}

private boolean isPositionHeader(int position) {
    return position == 0;
}

//设置viewPager
private void setViewPager(final HeaderHolder holder) {
    holder.viewPager.setAdapter(new HomeViewPagerAdapter(homeTopInfos));
    holder.indicator.setCircleNumber(homeTopInfos.size());
    holder.viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        }

        @Override
        public void onPageSelected(int position) {
            holder.indicator.setSelectdItem(position);
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            if (state == ViewPager.SCROLL_STATE_DRAGGING) {
                isTouchViewPager = true;
            } else {
                isTouchViewPager = false;
            }
        }
    });
    loopViewPager(holder);
}

//获取HomeStoriesList列表的条目,需要减去HeaderView
private HomeStoriesInfo getHomeStoriesListItem(int pos) {
    if (pos > 0) {
        return homeStoriesInfos.get(pos - 1);
    } else {
        throw new RuntimeException("the pos < 0");
    }
}

//让viewpager循环播放
private void loopViewPager(final HeaderHolder holder) {
    BaseApplication.getHandler().postDelayed(new Runnable() {
        @Override
        public void run() {
            if (isTouchViewPager == false) {
                int item = (holder.viewPager.getCurrentItem()) + 1;
                holder.viewPager.setCurrentItem(item % (holder.viewPager.getAdapter().getCount()));
                holder.indicator.setSelectdItem(item);
            }
            loopViewPager(holder);
        }
    }, 5000);
}

//显示日报条目的Holder
public class MyViewHolder extends RecyclerView.ViewHolder {
    public ImageView iv_title;
    public TextView tv_title;

    public MyViewHolder(View view) {
        super(view);
        iv_title = ButterKnife.findById(view, R.id.iv_title);
        tv_title = ButterKnife.findById(view, R.id.tv_title);
    }
}

//显示viewpager的Holder
public class HeaderHolder extends RecyclerView.ViewHolder {
    public ViewPager viewPager;
    public CircleIndicator indicator;

    public HeaderHolder(View view) {
        super(view);
        viewPager = ButterKnife.findById(view, R.id.viewPager);
        indicator = ButterKnife.findById(view, R.id.indicator);
    }
}

//显示日期的Holder
public class DateHolder extends RecyclerView.ViewHolder {
    public TextView tv_date;

    public DateHolder(View view) {
        super(view);
        tv_date = ButterKnife.findById(view, R.id.tv_date);
    }
}

//加载更多数据
public void add(List<HomeStoriesInfo> homeInfo) {
    LogUtils.e("add!!");
    if (homeInfo == null) {
        return;
    }
    homeStoriesInfos.addAll(homeInfo);
    this.notifyDataSetChanged();
}

public String getTitle(int pos) {
    if (pos >= 0 && pos < homeStoriesInfos.size()) {
        return homeStoriesInfos.get(pos).getDate();
    }
    return "";
}
}

下一篇要搞的问题是:

  • 点击条目显示具体的知乎日报信息怎么实现?

上拉刷新参考:

http://blog.csdn.net/lisdye2/article/details/51384159

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值