完成首页的基本功能之后,现在开始着手进入体系页面的功能实现,在体系页面中,主要是对Android整体的体系概括,有大体系下的子体系,我考虑使用RecyclerView + FlexBoxLayout实现。
1、RecyclerView的使用
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_system"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="70dp"
></androidx.recyclerview.widget.RecyclerView>
这应该是我在项目中第一次引进RecyclerView
,相对于ListView
来说,RecyclerView
性能更加高效,关键是支持瀑布流,在后续的功能中,我将会引入这个特性。
在了解RecyclerView
的情况下,大家应该都知道它的缓存体系是要比ListView
优越的,而且内部封装ViewHolder
,不需要我们自行设置,主持多种布局(viewtype
)3大缓存区…
使用起来也非常方便,需要设置适配器和布局管理器(不设置没显示)
//设置布局管理器
rv_system.setLayoutManager(new LinearLayoutManager(getContext()));
//设置RecyclerView适配器
rvAdapter = new SystemRecyclerViewAdapter(getContext(),data);
//设置适配器
rv_system.setAdapter(rvAdapter);
RecyclerView
适配器代码:
public class SystemRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private Context context;
private List<SystemBean.DataBean> data;
public SystemRecyclerViewAdapter(Context context, List<SystemBean.DataBean> data){
this.context = context;
this.data = data;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_viewpager_tab, null);
return new TabHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if(holder instanceof TabHolder){
((TabHolder) holder).tv_system_title.setText(data.get(position).getName());
//拿到当前position的child集合
List<SystemBean.DataBean.ChildrenBean> children = data.get(position).getChildren();
for (int i = 0; i < children.size(); i++) {
TextView tv = new TextView(context);
tv.setText(children.get(i).getName());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
);
params.rightMargin = 10;
params.topMargin = 10;
params.leftMargin = 10;
params.bottomMargin = 10;
tv.setLayoutParams(params);
((TabHolder) holder).fl_child.addView(tv);
}
}
}
@Override
public int getItemCount() {
return data.size();
}
class TabHolder extends RecyclerView.ViewHolder{
private TextView tv_system_title;
private FlexboxLayout fl_child;
public TabHolder(@NonNull View itemView) {
super(itemView);
tv_system_title = itemView.findViewById(R.id.tv_system_title);
fl_child = itemView.findViewById(R.id.fl_child);
}
}
}
完成到这里,显示就算完成了,但是有一个bug,来看一下:
现在看着没问题,在滑动的时候,再回到顶部看一下:
数据完全错乱,而且对着滑动的进行,数据更加混乱,已经没有刚开始加载页面时的状态。
这个问题的主要原因就是,前面说到了,RecyclerView的缓存机制,内置ViewHolder,当我们回到顶部,如果缓存中有该Item的ViewHolder缓存,那么就会复用缓存,没找到缓存,那么就重新BindView,这个时候就会出现一个问题,你在重新BindView的时候,所加载的数据,就不一定是当前Item的数据,可能是其他Item的数据,所以会出现数据错乱。
解决方法:设置item缓存数量,确保能存缓存中获取数据。
//设置缓存大小
rv_system.setItemViewCacheSize(data.size());
问题解决,看效果:
设置一下RecyclerView的item之间的宽度,通过调用RecyclerView
的addItemDecoration
方法,设置item之间的间距。
public class RvItemDistance extends RecyclerView.ItemDecoration {
private final int space;
public RvItemDistance(int space){
this.space = space;
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
outRect.left = space;
outRect.right = space;
outRect.bottom = space;
if(parent.getChildLayoutPosition(view) == 0){
outRect.top = space;
}
}
public static int px2dp(float dpValue) {
return (int) (0.5f + dpValue * Resources.getSystem().getDisplayMetrics().density);
}
}
//设置Item之间的间距
rv_system.addItemDecoration(new RvItemDistance(RvItemDistance.px2dp(8)));
但是在后续的开发过程中,会出现一个问题:随着数据的加载,Item之间的间距逐渐变大;
这个问题还是之前讲过的复用的问题,需要在item加载的时候,处理分割线的问题,因此改进如下:
if(rv_square.getItemDecorationCount() == 0) {
rv_square.addItemDecoration(new RvItemDistance(RvItemDistance.px2dp(6)));
}
2、单击体系下关键字,查看相关文章
加入我点击“开发环境”下“Android Studio”关键字,便可以查看当前该关键字下的相关文章,这和每个关键字携带的id有关,因此当我们点击每个关键字时,要返回它的id值。
在RecyclerView的适配器中,自己写个接口,回调即可。
public interface onTabClickedListener{
void onClick(int id);
}
private onTabClickedListener listener;
public void seOnTabClickedtListener(onTabClickedListener listener) {
this.listener = listener;
}
for (int i = 0; i < ((TabHolder) holder).fl_child.getChildCount(); i++) {
View childAt = ((TabHolder) holder).fl_child.getChildAt(i);
int id = children.get(i).getId();
childAt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onClick(id);
}
});
}
对于接口的回调,点击事件为什么这么写,我来解释一下:
在RecyclerView
的onBindViewHolder
方法中,是给每一个Item绑定数据,假如说position = 0
,那么就是给第1个Item绑定数据,实例化Item之后,将TextView
添加到FlexBoxLayout
中,所以在设置点击事件时,如果给TextView
设置单击事件,失效的原因就是,在手机屏幕中,不可能只显示当前一个Item,我们看到的可能有多个,如果给TextView设置单击事件监听,那么我们可能是给第2个Item设置了单击事件监听,也可能是第3个。
因为每个Item,都有一个FlexBoxLayout
,所以我们在当前Iposition中,获取FlexBoxLayout
中的子View,那么得到的一定是当前position的FlexBoxLayout
中的子View,所以应该给这个子View设置单击事件监听。
通过接口回调得到的值,当单击每个关键字时,就跳转到对应的关键字界面。
//获取id回调
rvAdapter.seOnTabClickedtListener(new SystemRecyclerViewAdapter.onTabClickedListener() {
@Override
public void onClick(int id,String name) {
Intent intent = new Intent();
intent.putExtra("ids",id);
intent.putExtra("title",name);
intent.setClass(getContext(), SystemArticleActivity.class);
startActivity(intent);
}
在关键字文章界面,我还是用到了RecyclerView
,把适配器源码贴出来吧。
public class SystemArticleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context context;
private final List<SystemArticleBean.DataBean.DatasBean> datas;
public SystemArticleAdapter(Context context, List<SystemArticleBean.DataBean.DatasBean> datas){
this.context = context;
this.datas = datas;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_system_article,parent,false);
return new SystemViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof SystemViewHolder) {
if(datas.get(position).getShareUser().equals("")){
((SystemViewHolder) holder).tv_shareuser.setText("官方分享");
}else {
((SystemViewHolder) holder).tv_shareuser.setText(datas.get(position).getShareUser());
}
((SystemViewHolder) holder).times.setText(datas.get(position).getNiceDate());
((SystemViewHolder) holder).tv_sys_title.setText(datas.get(position).getTitle());
}
}
@Override
public int getItemCount() {
return datas.size();
}
class SystemViewHolder extends RecyclerView.ViewHolder{
private ImageView iv_heart;
private TextView tv_shareuser,times,tv_sys_title;
public SystemViewHolder(@NonNull View itemView) {
super(itemView);
iv_heart = itemView.findViewById(R.id.iv_heart);
tv_shareuser = itemView.findViewById(R.id.tv_shareuser);
times = itemView.findViewById(R.id.times);
tv_sys_title = itemView.findViewById(R.id.tv_sys_title);
}
}
}
看下效果。
当然可以根据自己喜欢的形式自己创造,这些还是比较简单的。
单击感兴趣的文章,进入文章详情页面,RecyclerView没有单击事件,同样自己做接口回调。
public interface onItemClickListener{
void onClick(int position);
}
private onItemClickListener listener;
public void setOnItemClickListener(onItemClickListener listener) {
this.listener = listener;
}
通过回调得到当前的Item的position,获取文章的url地址,通过WebView加载url,这个内容在《玩Android项目开发4------首页(ListView实现文章置顶)》一节中有详细介绍。
当RecyclerView往上滑动,到最底层时,这个时候就需要加载显示下一页的数据,并且更新适配器的内容。
首先在适配器中,要判断,如果当前页面至少显示了5条数据,而且已经滑动到该页面的最后一个Item,那么就显示加载的进度条。
//滑动时
if(position > 5 && position == datas.size() -1){
((SquareViewHolder) holder).pb_load.setVisibility(View.VISIBLE);
}else{
((SquareViewHolder) holder).pb_load.setVisibility(View.GONE);
}
接下来监听RecyclerView是否滑动到底部,如果滑动到底部,那么就进行下一页的加载,我这里是自己做的接口回调。
//监听
aAdapter.setLoadCallback(new SquareAdapter.LoadCallback() {
@Override
public void onLoad(boolean lastPosition) {
if(lastPosition){
// Toast.makeText(getContext(),"到底了",Toast.LENGTH_SHORT).show();
int curPage = squareBean.getData().getCurPage();
Message message = Message.obtain();
message.what = LOAD_WHAT;
message.obj = curPage;
handler.sendMessageDelayed(message,1000);
}
}
});
当页面滑动到底部时,我需要知道当前页面的页数,然后回调去主线程更新适配器数据,加载到下一页。
switch (msg.what){
case LOAD_WHAT:
curPage = (int)msg.obj;
fPresenter.getSquareData(curPage);
fPresenter.getData().observe(getActivity(), new Observer<SquareBean>() {
@Override
public void onChanged(SquareBean squareBean) {
showSquareList(squareBean);
}
});
Log.e("TAG","curPage==="+curPage);
aAdapter.notifyDataSetChanged();
break;