1.首先看看TopActivity效果。
2.TopicActivity是一个继承BaseActivity的。前面分析过BaseActivity了。主要有一个标题栏,有返回的图标。
3.贴一下TopicActivity源代码。
/* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Last modified 2017-04-09 19:39:57 * * GitHub: https://github.com/GcsSloop * Website: http://www.gcssloop.com * Weibo: http://weibo.com/GcsSloop */ package com.gcssloop.diycode.activity; import android.content.Context; import android.content.Intent; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.view.View; import com.gcssloop.diycode.R; import com.gcssloop.diycode.base.app.BaseActivity; import com.gcssloop.diycode.base.app.ViewHolder; import com.gcssloop.diycode.fragment.NodeTopicListFragment; /** * 查看不同分类的 Topic */ public class TopicActivity extends BaseActivity { private static String Key_Node_ID = "Key_Node_ID"; private static String Key_Node_Name = "Key_Node_Name"; public static void newInstance(Context context, int nodeId, String nodeName) { Intent intent = new Intent(context, TopicActivity.class); intent.putExtra(Key_Node_ID, nodeId); intent.putExtra(Key_Node_Name, nodeName); context.startActivity(intent); } @Override protected int getLayoutId() { return R.layout.activity_fragment; } @Override protected void initViews(ViewHolder holder, View root) { Intent intent = getIntent(); int NodeId = intent.getIntExtra(Key_Node_ID, 0); String NodeName = intent.getStringExtra(Key_Node_Name); setTitle(NodeName); NodeTopicListFragment fragment = NodeTopicListFragment.newInstance(NodeId); FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.add(R.id.fragment, fragment); transaction.commit(); } }
4.首先有一个静态函数newInstance,主要就是方便外部调用打开这个Activity吧。
5.实现在BaseActivity中定义的抽象函数getLayoutId()==>主要作用就是得到一个布局的id。
这个布局activity_fragment源码贴一下吧。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_topic" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.gcssloop.diycode.activity.TopicActivity"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content"/> <FrameLayout android:id="@+id/fragment" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
预览效果就是两个部分,一个是一个Toolbar,大概占了1/8,剩下的是一个FrameLayout
6.实现在BaseActivity中定义的抽象函数initViews(ViewHolder holder,view root)==>主要是加载一些视图。
首先是得到外部传过来的NodeName,将ActionBar的标题通过函数setTitle来设置。
这里用了一个NodeTopicListFragment,不知道是什么东西?==>原来自己定义的一个碎片。
贴一下NodeTopicListFragment的定义。
/* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Last modified 2017-04-09 19:30:45 * * GitHub: https://github.com/GcsSloop * Website: http://www.gcssloop.com * Weibo: http://weibo.com/GcsSloop */ package com.gcssloop.diycode.fragment; import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import com.gcssloop.diycode.fragment.base.SimpleRefreshRecyclerFragment; import com.gcssloop.diycode.fragment.provider.TopicProvider; import com.gcssloop.diycode_sdk.api.topic.bean.Topic; import com.gcssloop.diycode_sdk.api.topic.event.GetTopicsListEvent; import com.gcssloop.recyclerview.adapter.multitype.HeaderFooterAdapter; /** * 分类 topic 列表 */ public class NodeTopicListFragment extends SimpleRefreshRecyclerFragment<Topic, GetTopicsListEvent> { private static String Key_Node_ID = "Key_Node_ID"; private int mNodeId = 0; public static NodeTopicListFragment newInstance(int nodeId) { Bundle args = new Bundle(); args.putInt(Key_Node_ID, nodeId); NodeTopicListFragment fragment = new NodeTopicListFragment(); fragment.setArguments(args); return fragment; } @Override public void initData(HeaderFooterAdapter adapter) { mNodeId = getArguments().getInt(Key_Node_ID, 0); loadMore(); } @Override protected void setAdapterRegister(Context context, RecyclerView recyclerView, HeaderFooterAdapter adapter) { adapter.register(Topic.class, new TopicProvider(getContext())); } @NonNull @Override protected String request(int offset, int limit) { return mDiycode.getTopicsList(null, mNodeId, offset, limit); } }
6.1但是这个NodeTopicListFragment继承了一个SimpleRefreshRecyclerFragment<Topic,GetTopicListEvent>又不知道什么意思呢!
贴一下SimpleRefreshRecyclerFragment代码吧。
/* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Last modified 2017-04-09 21:16:47 * * GitHub: https://github.com/GcsSloop * Website: http://www.gcssloop.com * Weibo: http://weibo.com/GcsSloop */ package com.gcssloop.diycode.fragment.base; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import com.gcssloop.recyclerview.layoutmanager.SpeedyLinearLayoutManager; import com.gcssloop.diycode_sdk.api.base.event.BaseEvent; import com.gcssloop.recyclerview.adapter.multitype.HeaderFooterAdapter; import java.util.List; public abstract class SimpleRefreshRecyclerFragment<T, Event extends BaseEvent<List<T>>> extends RefreshRecyclerFragment<T, Event> { @NonNull @Override protected RecyclerView.LayoutManager getRecyclerViewLayoutManager() { return new SpeedyLinearLayoutManager(getContext()); } @Override protected void onRefresh(Event event, HeaderFooterAdapter adapter) { adapter.clearDatas(); adapter.addDatas(event.getBean()); toast("刷新成功"); } @Override protected void onLoadMore(Event event, HeaderFooterAdapter adapter) { adapter.addDatas(event.getBean()); } @Override protected void onError(Event event, String postType) { if (postType.equals(POST_LOAD_MORE)) { toast("加载更多失败"); } else if (postType.equals(POST_REFRESH)) { toast("刷新数据失败"); } } }
6.1.1代码中第一段都无法理解:public abstract class SimpleRefreshRecyclerFragment<T,Event extends BaseEvent<List<T>>> extends RefreshRecyclerFragment<T,Event>。
首先理解一下这个BaseEvent。
/**
* 所有 Event 的基类
* <p>
* T 为对应的实体类
* <p>
* HTTP Status
* -1 - 可能是网络未连接
* 200, 201 - 请求成功,或执行成功
* 400 - 参数不符合 API 的要求、或者数据格式验证没有通过,请配合 Response Body 里面的 error 信息确定问题。
* 401 - 用户认证失败,或缺少认证信息,比如 access_token 过期,或没传,可以尝试用 refresh_token 方式获得新的 access_token。
* 403 - 当前用户对资源没有操作权限
* 404 - 资源不存在。
* 500 - 服务器异常
*/
然后它继承了一个RefreshRecycleFragment<T,Event>,这个也是在这个项目中的一个类,贴一下这个Fragment代码吧。
/* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Last modified 2017-04-09 21:16:47 * * GitHub: https://github.com/GcsSloop * Website: http://www.gcssloop.com * Weibo: http://weibo.com/GcsSloop */ package com.gcssloop.diycode.fragment.base; import android.content.Context; import android.support.annotation.NonNull; import android.support.v4.util.ArrayMap; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.RecyclerView; import android.view.View; import com.gcssloop.diycode.R; import com.gcssloop.diycode.base.app.ViewHolder; import com.gcssloop.diycode.fragment.bean.Footer; import com.gcssloop.diycode.fragment.provider.FooterProvider; import com.gcssloop.diycode_sdk.api.base.event.BaseEvent; import com.gcssloop.recyclerview.adapter.multitype.HeaderFooterAdapter; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.util.List; import static android.support.v7.recyclerview.R.styleable.RecyclerView; /** * 具有下拉刷新和上拉加载的 Fragment */ public abstract class RefreshRecyclerFragment<T, Event extends BaseEvent<List<T>>> extends BaseFragment { // 请求状态 - 下拉刷新 还是 加载更多 public static final String POST_LOAD_MORE = "load_more"; public static final String POST_REFRESH = "refresh"; private ArrayMap<String, String> mPostTypes = new ArrayMap<>(); // 请求类型 // 当前状态 private static final int STATE_NORMAL = 0; // 正常 private static final int STATE_NO_MORE = 1; // 正在 private static final int STATE_LOADING = 2; // 加载 private static final int STATE_REFRESH = 3; // 刷新 private int mState = STATE_NORMAL; // 分页加载 protected int pageIndex = 0; // 当面页码 protected int pageCount = 20; // 每页个数 // View private SwipeRefreshLayout mRefreshLayout; protected RecyclerView mRecyclerView; // 状态 private boolean refreshEnable = true; // 是否允许刷新 private boolean loadMoreEnable = true; // 是否允许加载 // 适配器 protected HeaderFooterAdapter mAdapter; protected FooterProvider mFooterProvider; protected boolean isFirstAddFooter = true; @Override protected int getLayoutId() { return R.layout.fragment_refresh_recycler; } @Override protected void initViews(ViewHolder holder, View root) { // 适配器 mAdapter = new HeaderFooterAdapter(); mFooterProvider = new FooterProvider(getContext()) { @Override public void needLoadMore() { if (isFirstAddFooter) { isFirstAddFooter = false; return; } loadMore(); } }; mFooterProvider.setFooterNormal(); mAdapter.registerFooter(new Footer(), mFooterProvider); // refreshLayout mRefreshLayout = holder.get(R.id.refresh_layout); mRefreshLayout.setProgressViewOffset(false, -20, 80); mRefreshLayout.setColorSchemeColors(getResources().getColor(R.color.diy_red)); mRefreshLayout.setEnabled(true); // RecyclerView mRecyclerView = holder.get(R.id.recycler_view); mRecyclerView.setHasFixedSize(true); mRecyclerView.setAdapter(mAdapter); mRecyclerView.setLayoutManager(getRecyclerViewLayoutManager()); setAdapterRegister(getContext(), mRecyclerView, mAdapter); // 监听 RefreshLayout 下拉刷新 mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { refresh(); } }); initData(mAdapter); } protected void refresh() { if (!refreshEnable) return; pageIndex = 0; String uuid = request(pageIndex * pageCount, pageCount); mPostTypes.put(uuid, POST_REFRESH); pageIndex++; mState = STATE_REFRESH; } protected void loadMore() { if (!loadMoreEnable) return; if (mState == STATE_NO_MORE) return; String uuid = request(pageIndex * pageCount, pageCount); mPostTypes.put(uuid, POST_LOAD_MORE); pageIndex++; mState = STATE_LOADING; mFooterProvider.setFooterLoading(); } @Subscribe(threadMode = ThreadMode.MAIN) public void onResultEvent(Event event) { String postType = mPostTypes.get(event.getUUID()); if (event.isOk()) { if (postType.equals(POST_LOAD_MORE)) { onLoadMore(event); } else if (postType.equals(POST_REFRESH)) { onRefresh(event); } } else { onError(event); } mPostTypes.remove(event.getUUID()); } protected void onRefresh(Event event) { mState = STATE_NORMAL; mRefreshLayout.setRefreshing(false); onRefresh(event, mAdapter); } protected void onLoadMore(Event event) { if (event.getBean().size() < pageCount) { mState = STATE_NO_MORE; mFooterProvider.setFooterNormal(); } else { mState = STATE_NORMAL; mFooterProvider.setFooterNormal(); } onLoadMore(event, mAdapter); } protected void onError(Event event) { mState = STATE_NORMAL; // 状态重置为正常,以便可以重试,否则进入异常状态后无法再变为正常状态 String postType = mPostTypes.get(event.getUUID()); if (postType.equals(POST_LOAD_MORE)) { mFooterProvider.setFooterError(new View.OnClickListener() { @Override public void onClick(View v) { pageIndex--; loadMore(); } }); } else if (postType.equals(POST_REFRESH)) { mRefreshLayout.setRefreshing(false); mFooterProvider.setFooterNormal(); } onError(event, postType); } public void setRefreshEnable(boolean refreshEnable) { this.refreshEnable = refreshEnable; mRefreshLayout.setEnabled(refreshEnable); } public void setLoadMoreEnable(boolean loadMoreEnable) { this.loadMoreEnable = loadMoreEnable; } public void quickToTop() { mRecyclerView.smoothScrollToPosition(0); } @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } //--- 需要继承类处理的部分 ---------------------------------------------------------------------- /** * 加载数初始化数据,可以从缓存或者其他地方加载, * 如果没有初始数据,一般调用 loadMore() 即可。 * * @param adapter 适配器 */ public abstract void initData(HeaderFooterAdapter adapter); /** * 为 RecyclerView 的 Adapter 注册数据类型 * 例如: adapter.register(Bean.class, new BeanProvider(getContext())); * * @param context 上下文 * @param recyclerView RecyclerView * @param adapter Adapter */ protected abstract void setAdapterRegister(Context context, RecyclerView recyclerView, HeaderFooterAdapter adapter); /** * 获取 RecyclerView 的 LayoutManager * 例如: return new LinerLayoutManager(context); * * @return LayoutManager */ @NonNull protected abstract RecyclerView.LayoutManager getRecyclerViewLayoutManager(); /** * 请求数据,并返回请求的 uuid * 例如:return mDiycode.getTopicsList(null, mNodeId, offset, limit); * * @param offset 偏移量 * @param limit 请求数量 * @return uuid */ @NonNull protected abstract String request(int offset, int limit); /** * 数据刷新成功的回调,由于不同页面可能要对数据进行处理,例如重新排序,清理掉一些无效数据等,所以由子类自己实现, * 如果不需要特殊处理,一般像下面这样写就行: * adapter.clearDatas(); * adapter.addDatas(event.geiBean()); * * @param event Event * @param adapter Adapter */ protected abstract void onRefresh(Event event, HeaderFooterAdapter adapter); /** * 数据加载成功时调用,如果不需要对数据进行特殊处理,这样写就行: * adapter.addDatas(event.getBean()); * * @param event Event * @param adapter Adapter */ protected abstract void onLoadMore(Event event, HeaderFooterAdapter adapter); /** * 数据加载错误时调用,你可以在这里获取错误类型并进行处理,如果不需要特殊处理,弹出一个 toast 提醒用户即可。 * if (postType.equals(POST_LOAD_MORE)) { * toast("加载更多失败"); * } else if (postType.equals(POST_REFRESH)) { * toast("刷新数据失败"); * } * * @param event * @param postType */ protected abstract void onError(Event event, String postType); }
然后这个RefreshRecylerFragment<T,Event extends BaseEvent<List<T>>>继承了一个BaseFragment,也是自己写的一个类。贴一下。
/* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Last modified 2017-04-09 21:16:47 * * GitHub: https://github.com/GcsSloop * Website: http://www.gcssloop.com * Weibo: http://weibo.com/GcsSloop */ package com.gcssloop.diycode.fragment.base; import android.os.Bundle; import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import com.gcssloop.diycode.base.app.ViewHolder; import com.gcssloop.diycode.utils.Config; import com.gcssloop.diycode.utils.DataCache; import com.gcssloop.diycode_sdk.api.Diycode; /** * 提供基础内容和生命周期控制 */ public abstract class BaseFragment extends Fragment { private ViewHolder mViewHolder; // View 管理 // 数据 protected Config mConfig; // 配置(状态信息) protected Diycode mDiycode; // 在线(服务器) protected DataCache mDataCache; // 缓存(本地) @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mConfig = Config.getSingleInstance(); mDiycode = Diycode.getSingleInstance(); mDataCache = new DataCache(getContext()); } @LayoutRes protected abstract int getLayoutId(); public ViewHolder getViewHolder() { return mViewHolder; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { mViewHolder = new ViewHolder(inflater, container, getLayoutId()); return mViewHolder.getRootView(); } protected abstract void initViews(ViewHolder holder, View root); @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); initViews(mViewHolder, mViewHolder.getRootView()); } protected void toast(String text) { Toast.makeText(getContext(), text, Toast.LENGTH_SHORT).show(); } }
BaseFragment中定义了4个变量。
这里有3个重要的类,Config定义在Utils中,DataCache也定义在Utils中,都是自己写的类,比较复杂,先不贴了。
然后在BaseFragement中还有一个onCreate函数,定义了一些初始化的东西。
然后定义了一个抽象函数,用来获得布局资源的id。
然后一个onCreateView函数,用来获得mViewHolder的。
然后又定义了一个抽象函数初始化ViewHolder的。在继承者中必须实现的。
然后执行到onActivityCreated,在 执行initViews()函数。
然后这里面定义一个toast函数。
好了,现在又回到RefreshRecyclerFragment中了。==>其实它的作用就是具有下拉刷新和上拉加载的Fragment。和数据一点耦合也没有。因为是继承BaseFragment的,只要实现BaseFragment的的两个抽象方法,用来处理数据,其实就是获得数据,然后利用这个可以下拉刷新和上拉加载的Fragment来处理即可。
然后这里面有两个适配器。
==>主要作用就是刷新的一些视图,比价刷新成功,加载失败,加载完毕等。
然后实现了BaseFragment中的抽象方法==>getLayoutId(),加载的布局文件为:R.layout.fragment_refresh_recycler==>代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/refresh_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content"/> </android.support.v4.widget.SwipeRefreshLayout> </LinearLayout>
这里面有一个SwipeRefreshLayout其实就是那个下拉刷新的图标,id为:refresh_layout。
然后实现了BaseFragment中的抽象方法==>initViews(ViewHolder holder,View root)从适配器中获得数据。然后在RefreshRecyclerFragment修改了刷新图标的颜色和偏移量。
这里有布局中id叫做recycler_view的RecyclerView非常关键,如果没有setAdapter,那么所有数据都无法显示了。mAdapter事先注册了一个mFooterProvider数据的。
然后实现了两个抽象方法:
需要在继承者中实现。然后是监听RefreshLayout下拉刷新:
如何刷新呢?
request函数也是一个抽象函数,在继承者中具体实现。
然后是加载更多==>loadMore()
好像都是先一个request函数,返回一个字段,然后将这个字段放在本地的一个ArrayMap中,这个是请求类型,第二个字段是请求类型是“load_more”或者“refresh”。
这里使用了一个注解:@Subscribe(threadMode=ThreadMode.MAIN)==>表示该函数在主线程即UI线程中执行。 这里是返回事件,即成功刷新,或者成功加载,或者刷新失败,加载失败后的结果。
其实这里也只是过渡,其实还是没有处理数据,还是交给继承者来处理。真正的刷新和加载要和mAdapter挂钩的呢。
这里有一个onError方法,先处理一些状态,判断是刷新还是加载更多,真正的出错还要交给继承者来处理。
然后是设置一些基本方法,包括能否刷新,能否加载更多,能否到返回到顶部。 然后是进入onStart()和onStop()环节了。
==>EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化Android事件传递,这里的事件可以理解为消息。事件传递可以用于Android四大组件间通讯,也可以用于异步线程和主线程间通讯等。传统的事件传递方式包括:Handler,BroadcastReceiver,Interface回调,相比之下EventBus的特点是代码简洁,使用简单,并将事件发布和订阅充分解耦。
然后是需要继承类处理的部分 第一个抽象函数是加载初始化数据。 第二个抽象函数是注册数据类型。
第三个抽象函数获取RecyclerView的LayoutManager。
第四个抽象函数是:请求数据了,返回请求的uuid。
第五个抽象函数是onRefresh==>数据刷新成功后的回调。
第六个抽象函数是onLoadMore==>数据加载成功是的回调。
第七个抽象函数是:onError==>数据加载错误时调
6.1.2.回到SimpleRefreshRecyclerFragment。其实这三个类是一层接一层
==>BaseFragment->RefreshRecycleFragment->SimpleRefreshRecyclerFragment。
这个SimpleRefreshRecycleFragment也只是一个抽象类,所以虽然继承了RefreshRecycleFragment,但是并没有时间所有7个抽象方法,只实现了4个。 6.1.3.第一个是getRecyclerViewLayoutManager==> 这个SpeedyLinearLayoutManager是什么鬼?自己定义的一个类,附上源代码吧!
/* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Last modified 2017-04-10 20:16:42 * * GitHub: https://github.com/GcsSloop * Website: http://www.gcssloop.com * Weibo: http://weibo.com/GcsSloop */ package com.gcssloop.recyclerview.layoutmanager; import android.content.Context; import android.graphics.PointF; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearSmoothScroller; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.util.DisplayMetrics; /** * 支持快速返回的 LinerLayoutManager */ public class SpeedyLinearLayoutManager extends LinearLayoutManager { private static final float MILLISECONDS_PER_INCH = 6f; //default is 25f (bigger = slower) public SpeedyLinearLayoutManager(Context context) { super(context); } public SpeedyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } public SpeedyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView .getContext()) { @Override public PointF computeScrollVectorForPosition(int targetPosition) { return SpeedyLinearLayoutManager.this.computeScrollVectorForPosition (targetPosition); } @Override protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; } }; linearSmoothScroller.setTargetPosition(position); startSmoothScroll(linearSmoothScroller); } }
其实是自定义的LinearLayoutManager,这又是啥? ==>就是一个线性布局管理器,这里用到快速到顶部。
这里复写3个构造函数,在Override一个smoothScrollToPosition函数即可。这是一个支持快速返回的LinearLayoutManager。 如下图是Override的smoothSrollToPosition函数。
这里有一个LinearSmoothScroller,作用就是决定视图滑动的一些属性。这里有一个MILLISECONDS_PER_INCH决定了滑动的速度。如果设置得很大的话,滑动的就比较慢,如果设置的值很小,滑动的速度就很快。
6.1.4.然后回到SimpleRefreshRecyclerFragment中,第二个Override的函数是onRefresh(),
adapter首先清空,然后添加数据。
这里先了解一下HeaderFooterAdapter。源代码如下:
/* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Last modified 2017-04-08 16:14:18 * * GitHub: https://github.com/GcsSloop * WeiBo: http://weibo.com/GcsSloop * WebSite: http://www.gcssloop.com */ package com.gcssloop.recyclerview.adapter.multitype; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; import com.gcssloop.recyclerview.adapter.base.RecyclerViewHolder; import java.util.ArrayList; import java.util.List; /** * 带有头部和底部的适配器 * 使用步骤: * 1. 创建实体类 Bean * 2. 创建对应的 provider 并继承自 BaseViewProvider, 在对应的 provider 的 onBindView 里面处理内容 * 3. 使用 adapter.register(bean, provider.class) 来将数据实体和 provider 对应起来 * 4. 注册 Header 或者 Footer * 5. 将数据 data 使用 ArrayList<Object> 类型存储起来, 使用 adapter.addDatas(data) 添加数据 * 6. 大功告成 */ public class HeaderFooterAdapter extends RecyclerView.Adapter<RecyclerViewHolder> implements TypePool { private List<Object> mItems = new ArrayList<>(); private MultiTypePool mTypePool; private boolean hasHeader = false; private boolean hasFooter = false; public HeaderFooterAdapter() { mTypePool = new MultiTypePool(); } @Override public int getItemCount() { assert mItems != null; return mItems.size(); } @Override public int getItemViewType(int position) { assert mItems != null; Object item = mItems.get(position); int index = mTypePool.indexOf(item.getClass()); if (index >= 0) { return index; } return mTypePool.indexOf(item.getClass()); } @Override public void register(@NonNull Class<?> clazz, @NonNull BaseViewProvider provider) { mTypePool.register(clazz, provider); } public void registerHeader(@NonNull Object object, @NonNull BaseViewProvider provider) { if (hasHeader) return; mTypePool.register(object.getClass(), provider); mItems.add(0, object); hasHeader = true; notifyDataSetChanged(); } public void unRegisterHeader() { if (!hasHeader) return; mItems.remove(0); hasHeader = false; notifyDataSetChanged(); } public void registerFooter(@NonNull Object object, @NonNull BaseViewProvider provider) { if (hasFooter) return; mTypePool.register(object.getClass(), provider); mItems.add(object); hasFooter = true; notifyDataSetChanged(); } public void unRegisterFooter() { if (!hasFooter) return; mItems.remove(mItems.size() - 1); hasFooter = false; notifyDataSetChanged(); } @Override public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int indexViewType) { BaseViewProvider provider = getProviderByIndex(indexViewType); return provider.onCreateViewHolder(parent); } @Override public void onBindViewHolder(RecyclerViewHolder holder, int position) { assert mItems != null; Object item = mItems.get(position); BaseViewProvider provider = getProviderByClass(item.getClass()); provider.onBindView(holder, item); } @Override public int indexOf(@NonNull Class<?> clazz) { return mTypePool.indexOf(clazz); } @Override public List<BaseViewProvider> getProviders() { return mTypePool.getProviders(); } @Override public BaseViewProvider getProviderByIndex(int index) { return mTypePool.getProviderByIndex(index); } @Override public <T extends BaseViewProvider> T getProviderByClass(@NonNull Class<?> clazz) { return mTypePool.getProviderByClass(clazz); } public void addDatas(List<?> items) { if (hasFooter) { mItems.addAll(mItems.size() - 1, items); } else { mItems.addAll(items); } notifyDataSetChanged(); } /** * 获取纯数据 (不包含 Header 和 Footer) */ public List<Object> getDatas() { int startIndex = 0; int endIndex = mItems.size(); if (hasHeader) { startIndex++; } if (hasFooter) { endIndex--; } return mItems.subList(startIndex, endIndex); } /** * 获取全部数据 (包含 Header 和 Footer) */ public List<Object> getFullDatas() { return mItems; } public void clearDatas() { int startIndex = 0; int endIndex = mItems.size(); if (hasHeader) { startIndex++; } if (hasFooter) { endIndex--; } for (int i = endIndex - 1; i >= startIndex; i--) { mItems.remove(i); } notifyDataSetChanged(); } }
这是一个带有头部和底部的适配器。这个类其实是在另外一个模块中的。现在先不了解了。
这里之后直接用adapter.addDatas(event.getBean)添加了数据。注意:这里不仅仅只是添加了一个bean,而是一个List<T>,所以可能会有很多数据都放在这里面了。而adapter只要调用一个addDatas方法就行了。
6.1.5.然后是一个onLoadMore方法。
6.1.6.最后是一个onError方法。
6.2.终于回到NodeTopicListFragment中了。它继承了一个Topic的bean类。看一下Topic的定义吧。
/* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Last modified 2017-03-08 01:01:18 * * GitHub: https://github.com/GcsSloop * Website: http://www.gcssloop.com * Weibo: http://weibo.com/GcsSloop */ package com.gcssloop.diycode_sdk.api.topic.bean; import com.gcssloop.diycode_sdk.api.base.bean.Abilities; import com.gcssloop.diycode_sdk.api.user.bean.User; import java.io.Serializable; /** * topic 简略信息 */ public class Topic implements Serializable { private int id; // 唯一 id private String title; // 标题 private String created_at; // 创建时间 private String updated_at; // 更新时间 private String replied_at; // 最近一次回复时间 private int replies_count; // 回复总数量 private String node_name; // 节点名称 private int node_id; // 节点 id private int last_reply_user_id; // 最近一次回复的用户 id private String last_reply_user_login; // 最近一次回复的用户登录名 private User user; // 创建该话题的用户(信息) private boolean deleted; // 是否是被删除的 private boolean excellent; // 是否是加精的 private Abilities abilities; // 当前用户对该话题拥有的权限 public void setId(int id) { this.id = id; } public int getId() { return this.id; } public void setTitle(String title) { this.title = title; } public String getTitle() { return this.title; } public void setCreated_at(String created_at) { this.created_at = created_at; } public String getCreated_at() { return this.created_at; } public void setUpdated_at(String updated_at) { this.updated_at = updated_at; } public String getUpdated_at() { return this.updated_at; } public void setReplied_at(String replied_at) { this.replied_at = replied_at; } public String getReplied_at() { return this.replied_at; } public void setReplies_count(int replies_count) { this.replies_count = replies_count; } public int getReplies_count() { return this.replies_count; } public void setNode_name(String node_name) { this.node_name = node_name; } public String getNode_name() { return this.node_name; } public void setNode_id(int node_id) { this.node_id = node_id; } public int getNode_id() { return this.node_id; } public void setLast_reply_user_id(int last_reply_user_id) { this.last_reply_user_id = last_reply_user_id; } public int getLast_reply_user_id() { return this.last_reply_user_id; } public void setLast_reply_user_login(String last_reply_user_login) { this.last_reply_user_login = last_reply_user_login; } public String getLast_reply_user_login() { return this.last_reply_user_login; } public void setUser(User user) { this.user = user; } public User getUser() { return this.user; } public void setDeleted(boolean deleted) { this.deleted = deleted; } public boolean getDeleted() { return this.deleted; } public void setExcellent(boolean excellent) { this.excellent = excellent; } public boolean getExcellent() { return this.excellent; } public void setAbilities(Abilities abilities) { this.abilities = abilities; } public Abilities getAbilities() { return this.abilities; } @Override public boolean equals(Object obj) { if (!(obj instanceof Topic)) { return false; } Topic temp = (Topic) obj; if (temp.getId() == getId()) { return true; } return false; } }
这个也是在diycode_sdk中的API中定义的。
总共有14个信息。这里面有一个Override==>判断一个对象是否是Topic。
6.3.==>T对应Topic,==>GetTopicsListEvent对应BaseEvent<List<Topic>>(其实GetTopicsListEvent就是继承BaseEvent<List<Topic>>的),这些在sdk-API中已经表现,就不深究了。
6.4.首先一个静态方法==>newInstance(int nodeId)返回一个NodeTopicListFragment。
返回一个fragment,可以知道,这个Fragment已经具备了下拉刷新,上拉加载更多的功能。具备了返回到顶部的功能。具备了toast功能。
6.5.实现了在RefreshRecycleFragment中定义的抽象方法==>initData()
6.6.实现了在RefreshRecycleFragment中定义的抽象方法==>setAdapterRegister()
6.7.实现在RefreshRecycleFragment中定义的抽象方法==>request(),这里是调用API了。
7.回到TopicActivity中。进入initViews方法。
其实NodeTopicListFragment就是一个Fragment,可以刷新和加载更多的Fragment,同时也设置了Adapter的注册数据类型,还利用API调用了请求方法。
8.Android Fragment动态添加 ==>就是将上面那个碎片添加到topicActivity中。
总结一下吧:
1.首先这个TopicActivity是继承一个BaseActivity的。就是一个标题栏中左上角,其实这个BaseActivity就是处理标题栏的吧。
2.然后又一个关键的静态函数newInstance,就是实例化一个Topic吧。可能是外部函数想调用话题这个活动,这个函数的意义就是跳转到话题这个活动。
3.然后是得到布局的id,这个也是BaseActivity中定义的抽象函数,就是整个界面的布局函数,有一个Toolbar,然后是一个FrameLayout占据了其他所有空间。
4.然后是实现了BaseActivity中定义的抽象函数,初始化视图,将多次继承的fragment赋给这个FrameLayout。就是将碎片给到这个FrameLayout。采用了动态添加方法。
5.其实,事先,要从Intent中获得NodeId,才能利用这个NodeId,获得相应的碎片,调用了NodeTopicListFragment的newInstance方法,NodeId就是里面的参数。然后从Intent从获得的NodeName作为标题栏的Title。
6.这个NodeTopicListFragment其实是非常复杂的,它首先是继承了SimpleRefreshRecyclerFragment<Topic,GetTopicsListEvent>类的,这个SimpleRefreshRecyclerFragment是有一个静态的newInstanse方法,供别人调用。然后是实现了3个抽象方法,分别是initData(),setAdapterRegister(),request()三个函数。
7.SimpleRefreshRecyclerFragment继承了RefreshRecyclerFragment<T,Event>这个Event是从它自己的构造函数中的Event extends BaseEvent<List<T>>获得的,然后这个BaseEvent其实是API中定义的。然后这个SimpleRefreshRecyclerFragment实现了4个抽象方法,都是和加载或者刷新有关系的。
8.RefreshRecyclerFragment<T,Event extends BaseEvent<List<T>>> 继承了BaseFragment。它是一个具有下拉刷新和上拉加载的Fragment。这个类比较关键,定义了很多抽象函数。主要处理刷新和加载的根本逻辑。包括刷新的图标的颜色,一页最多有多少个数据行。刷新后的状态的变更,和加载成功或者失败后状态的变更。以及EventBus的注册和解注册。
9.BaseFragment就是继承Fragment的了。这个就是最基本的碎片了。里面有一个配置状态信息的Config类。有一个在线服务器Diycode类,有一个DataCache缓存本地数据的类。然后定义了两个抽象方法和一个onCreateView函数获得ViewHolder,一个onCreate函数初始化一些数据,onActivityCreate函数初始化视图。
10.其实这个TopicActivity和很多东西挂钩,现在了解了最外层的东西,知道了基本流程,但是很多细节还是要深究一下,比如刷新和加载,还有自定义的Adapter都是涉及到的东西比较多的。