Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 高仿知乎日報(三)[主頁面上拉刷新]

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

編輯:關於Android編程

主頁面上拉刷新實現

接著上一篇的問題:

RecylerView 如何上拉加載更多條目?

我們來理一下這個思路吧,看看需要我們來實現什麼?

UI上需要在RecylerView列表上添加ScrollListener,增加上拉的邏輯判斷,並添加實現? RecylerView滑動的過程中,ToolBar的Title文本需要更新為當天日報條目對應的日期? RecylerView需要對兩種條目進行處理,顯示日期和當前日期日報條目這兩種Item? 下拉過程中,對前一天條目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 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> {

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 paserJsonData(String json) {
    LogUtils.e("HomeProtocol paserJsonData");
    try {
        JSONObject jsonObject = new JSONObject(json);
        String date = jsonObject.getString("date");
        JSONArray jsonArrayStories = jsonObject.getJSONArray("stories");
        List 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 {
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 homeStoriesInfos;
private List homeTopInfos;
private List homeInfoList;

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

public HomeRecylerViewAdapter(List 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 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 "";
}
}

下一篇要搞的問題是:

點擊條目顯示具體的知乎日報信息怎麼實現?
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved