編輯:關於Android編程
接著上一篇的問題:
RecylerView 如何上拉加載更多條目?我們來理一下這個思路吧,看看需要我們來實現什麼?
UI上需要在RecylerView列表上添加ScrollListener,增加上拉的邏輯判斷,並添加實現? RecylerView滑動的過程中,ToolBar的Title文本需要更新為當天日報條目對應的日期? RecylerView需要對兩種條目進行處理,顯示日期和當前日期日報條目這兩種Item? 下拉過程中,對前一天條目Json數據的獲取和保存怎麼實現?以上就是我遇到的問題,一個一個解決吧!
首先,上拉的過程中,需要到最後一個條目才會去加載。因此我們需要判斷此時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 "";
}
}
下一篇要搞的問題是:
點擊條目顯示具體的知乎日報信息怎麼實現?
創建一個新項目是很簡單的,只要你安裝了Eclipse插件,並且你的Eclipse軟件版本在3.2或3.3,你就可以開始開發了。 首先, 看一下要創建"Hell
終於拋棄了卡頓惡心的Eclipse,在使用一個月Android Studio 之後,對Android Studio 的設計實在是非常敬佩,雖然現在Android
(一)前言Binder原本是IPC工具,但是在Android中它的主要作用是支持RPC(Remote Procedure Call),使得當前進程調用另一個進程的函數就像
在Android中,JSBridge已經不是什麼新鮮的事物了,各家的實現方式也略有差異。大多數人都知道WebView存在一個漏洞,見WebView中接口隱患與手機掛馬利用