ViewPagerWithRecyclerDemo【RecyclerView+ViewPager实现类似TabLayout+ViewPager效果】
版权声明:本文为HaiyuKing原创文章,转载请注明出处!
前言
使用RecyclerView+ViewPager实现类似TabLayout+ViewPager效果。
效果图
使用步骤
一、项目组织结构图
注意事项:
1、 导入类文件后需要change包名以及重新import R文件路径
2、 Values目录下的文件(strings.xml、dimens.xml、colors.xml等),如果项目中存在,则复制里面的内容,不要整个覆盖
二、导入步骤
(1)在build.gradle中引用recyclerview【版本号和appcompat保持一致】
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.why.project.viewpagerwithrecyclerdemo"
minSdkVersion 16
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
//RecyclerView
compile "com.android.support:recyclerview-v7:27.1.1"
}
(2)在项目中实现Recyclerview基本数据展现【顶部选项卡区域】
1、创建Bean类
package com.why.project.viewpagerwithrecyclerdemo.bean; /** * Created by HaiyuKing * Used */ public class TabItemBean { private String tabTitle; private String tabUrl; public TabItemBean(String tabTitle, String tabUrl){ this.tabTitle = tabTitle; this.tabUrl = tabUrl; } public String getTabTitle() { return tabTitle; } public void setTabTitle(String tabTitle) { this.tabTitle = tabTitle; } public String getTabUrl() { return tabUrl; } public void setTabUrl(String tabUrl) { this.tabUrl = tabUrl; } }
2、创建Adapter以及item的布局文件,同时定义颜色、文字大小值
package com.why.project.viewpagerwithrecyclerdemo.adapter; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.RelativeLayout; import android.widget.TextView; import com.why.project.viewpagerwithrecyclerdemo.R; import com.why.project.viewpagerwithrecyclerdemo.bean.TabItemBean; import java.util.ArrayList; /** * Created by HaiyuKing * Used */ public class TabAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { /**上下文*/ private Context myContext; /**集合*/ private ArrayList<TabItemBean> listitemList; private int selectedPosition = -1;//选中的列表项 /** * 构造函数 */ public TabAdapter(Context context, ArrayList<TabItemBean> itemlist) { myContext = context; listitemList = itemlist; } /** * 获取总的条目数 */ @Override public int getItemCount() { return listitemList.size(); } /** * 创建ViewHolder */ @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(myContext).inflate(R.layout.tab_list_item, parent, false); ItemViewHolder itemViewHolder = new ItemViewHolder(view); return itemViewHolder; } /** * 声明grid列表项ViewHolder */ static class ItemViewHolder extends RecyclerView.ViewHolder { public ItemViewHolder(View view) { super(view); listItemLayout = (RelativeLayout) view.findViewById(R.id.listitem_layout); mTabTitle = (TextView) view.findViewById(R.id.top_title); mTabUnderLine = (TextView) view.findViewById(R.id.top_underline); } RelativeLayout listItemLayout; TextView mTabTitle; TextView mTabUnderLine; } /** * 将数据绑定至ViewHolder */ @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int index) { //判断属于列表项 if (viewHolder instanceof ItemViewHolder) { TabItemBean tabItemBean = listitemList.get(index); final ItemViewHolder itemViewHold = ((ItemViewHolder) viewHolder); itemViewHold.mTabTitle.setText(tabItemBean.getTabTitle()); //设置下划线的宽度值==文本的宽度值 itemViewHold.mTabTitle.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); Log.w("why", "top_title.getMeasuredWidth()="+itemViewHold.mTabTitle.getMeasuredWidth()); itemViewHold.listItemLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); Log.w("why", "toptabLayout.getMeasuredWidth()="+itemViewHold.listItemLayout.getMeasuredWidth()); itemViewHold.mTabUnderLine.setWidth(itemViewHold.mTabTitle.getMeasuredWidth());//手动设置下划线的宽度值 if(selectedPosition == index){ itemViewHold.mTabTitle.setTextColor(myContext.getResources().getColor(R.color.tab_text_selected_top)); itemViewHold.mTabUnderLine.setBackgroundColor(myContext.getResources().getColor(R.color.tab_auto_selected_top)); }else{ itemViewHold.mTabTitle.setTextColor(myContext.getResources().getColor(R.color.tab_text_normal_top)); itemViewHold.mTabUnderLine.setBackgroundColor(myContext.getResources().getColor(R.color.tab_auto_normal_top)); } //如果设置了回调,则设置点击事件 if (mOnItemClickLitener != null) { itemViewHold.listItemLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { int position = itemViewHold.getLayoutPosition();//在增加数据或者减少数据时候,position和index就不一样了 mOnItemClickLitener.onItemClick(itemViewHold.listItemLayout, position); setSelected(position); } }); } } } /**更改选中的列表项下标值*/ public void setSelected(int selected) { this.selectedPosition = selected; notifyDataSetChanged(); } /** * 添加Item--用于动画的展现 */ public void addItem(int position, TabItemBean listitemBean) { listitemList.add(position, listitemBean); notifyItemInserted(position); } /** * 删除Item--用于动画的展现 */ public void removeItem(int position) { listitemList.remove(position); notifyItemRemoved(position); } /*=====================添加OnItemClickListener回调================================*/ public interface OnItemClickLitener { void onItemClick(View view, int position); } private OnItemClickLitener mOnItemClickLitener; public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener) { this.mOnItemClickLitener = mOnItemClickLitener; } }
<?xml version="1.0" encoding="utf-8"?> <!-- 带有下划线的顶部选项卡子项的布局文件--> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/listitem_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:paddingTop="@dimen/tab_top_auto_padding" android:paddingLeft="@dimen/tab_top_auto_padding" android:paddingRight="@dimen/tab_top_auto_padding" > <!-- 标题 --> <TextView android:id="@+id/top_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="" android:textSize="@dimen/tab_top_auto_title_size" android:textColor="@color/tab_text_normal_top" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" /> <!-- 下划线--> <!-- android:background="@color/tab_underline_selected_top" --> <TextView android:id="@+id/top_underline" android:layout_width="match_parent" android:layout_height="@dimen/tab_top_auto_height" android:background="@color/tab_auto_normal_top" android:layout_below="@id/top_title" android:layout_centerHorizontal="true" android:layout_marginTop="@dimen/tab_top_auto_padding" /> </RelativeLayout>
在colors.xml文件中添加以下代码
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#303F9F</color> <color name="colorAccent">#FF4081</color> <!-- *********************************顶部选项卡区域********************************* --> <!-- 顶部选项卡下划线背景色 --> <color name="tab_auto_normal_top">#00ffffff</color> <color name="tab_auto_selected_top">#3090d9</color> <!-- 顶部选项卡文本颜色 --> <color name="tab_text_normal_top">#191919</color> <color name="tab_text_selected_top">#3090d9</color> </resources>
在dimens.xml文件中添加以下代码
<resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <!-- *********************************顶部选项卡区域********************************* --> <!-- 选项卡的内边距 --> <dimen name="tab_top_auto_padding">10dp</dimen> <!-- 选项卡标题的文字大小 --> <dimen name="tab_top_auto_title_size">18sp</dimen> <!-- 选项卡标题的下划线高度 --> <dimen name="tab_top_auto_height">3dp</dimen> </resources>
3、在Activity布局文件中引用Recyclerview控件【见下文】
4、在Activity类中初始化recyclerview数据【见下文】
(3)创建需要用到的fragment类和布局文件
(4)引入MyCustomViewPager并参考MyCustomViewPagerAdapter创建viewpager的适配器
package com.why.project.viewpagerwithrecyclerdemo.adapter; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; import android.view.ViewGroup; import java.util.List; /** * Created by HaiyuKing * Used */ public class MyTabViewPagerAdapter extends FragmentStatePagerAdapter { /**碎片集合*/ private List<Fragment> fragmentList; public MyTabViewPagerAdapter(FragmentManager fm) { super(fm); // TODO Auto-generated constructor stub } /** * 自定义构造函数:用于传递碎片集合过来 * 一般都写上*/ public MyTabViewPagerAdapter(FragmentManager fm,List<Fragment> fragmentList) { super(fm); this.fragmentList = fragmentList; } @Override public Fragment getItem(int arg0) { // TODO Auto-generated method stub return fragmentList.get(arg0); } @Override public int getCount() { return fragmentList.size(); } @Override public void destroyItem(ViewGroup container, int position, Object object) { //viewpager+fragment来回滑动fragment重新加载的简单解决办法:http://blog.csdn.net/qq_28058443/article/details/51519663 //建议保留,因为首页碎片界面含有6个界面切换,总不能设置setOffscreenPageLimit(6)吧,而且设置个数少的话,销毁后还得重新加载 //super.destroyItem(container, position, object); } }
(5)因为Demo中使用到了webview请求网址,所以需要在AndroidManifest.xml中添加权限
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.why.project.viewpagerwithrecyclerdemo"> <!-- ======================授权访问网络(HttpUtil)========================== --> <!-- 允许程序打开网络套接字 --> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest>
三、使用方法
(1)在布局文件中引入RecyclerView和MyCustomViewPager【注意,修改MyCustomViewPage的完整路径】
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 横向列表 --> <android.support.v7.widget.RecyclerView android:id="@+id/tab_top_recycler" android:layout_width="match_parent" android:layout_height="wrap_content" /> <!-- 横分割线 --> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#e9e9e9" /> <!-- viewpager区域,配合Fragment使用,且不能滑动 --> <com.why.project.viewpagerwithrecyclerdemo.viewpager.MyCustomViewPager android:id="@+id/vp_tab" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout>
(2)在Activity中填充数据,关联RecyclerView和ViewPager
package com.why.project.viewpagerwithrecyclerdemo; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import com.why.project.viewpagerwithrecyclerdemo.adapter.MyTabViewPagerAdapter; import com.why.project.viewpagerwithrecyclerdemo.adapter.TabAdapter; import com.why.project.viewpagerwithrecyclerdemo.bean.TabItemBean; import com.why.project.viewpagerwithrecyclerdemo.fragment.WebViewFragment; import com.why.project.viewpagerwithrecyclerdemo.viewpager.MyCustomViewPager; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); //分类列表 private RecyclerView mTabRecycleView; private ArrayList<TabItemBean> mTabItemBeanArrayList; private TabAdapter mTabAdapter; /**保存的选项卡的下标值*/ private int savdCheckedIndex = 0; /**当前的选项卡的下标值*/ private int mCurrentIndex = -1; //横向滚动的ViewPager private MyCustomViewPager mViewPager; /**滑屏适配器*/ private MyTabViewPagerAdapter viewPagerAdapter; /**碎片集合*/ private List<Fragment> fragmentList; //碎片界面 private WebViewFragment mWebViewFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化控件以及设置 initView(); //初始化数据 initData(); //初始化控件的点击事件 initEvent(); } @Override protected void onResume() { super.onResume(); Log.e(TAG,"{onResume}savdCheckedIndex="+savdCheckedIndex); //设置保存的或者初始的选项卡标红显示 setTabsDisplay(savdCheckedIndex); mCurrentIndex = -1;//解决按home键后长时间不用,再次打开显示空白的问题 //设置保存的或者初始的选项卡展现对应的fragment setFragmentDisplay(savdCheckedIndex); } /** * 初始化控件 */ private void initView() { mTabRecycleView = findViewById(R.id.tab_top_recycler); mViewPager = (MyCustomViewPager) findViewById(R.id.vp_tab); } /** * 初始化数据 */ public void initData() { //初始化集合 mTabItemBeanArrayList = new ArrayList<TabItemBean>(); mTabItemBeanArrayList.add(new TabItemBean("百度","http://www.baidu.com")); mTabItemBeanArrayList.add(new TabItemBean("CSDN","http://blog.csdn.net")); mTabItemBeanArrayList.add(new TabItemBean("博客园","http://www.cnblogs.com")); mTabItemBeanArrayList.add(new TabItemBean("极客头条","http://geek.csdn.net/mobile")); mTabItemBeanArrayList.add(new TabItemBean("优设","http://www.uisdc.com/")); mTabItemBeanArrayList.add(new TabItemBean("玩Android","http://www.wanandroid.com/index")); mTabItemBeanArrayList.add(new TabItemBean("掘金","https://juejin.im/")); //设置布局管理器【表情分类列表】 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false); mTabRecycleView.setLayoutManager(linearLayoutManager); //设置适配器 if(mTabAdapter == null){ //设置适配器 mTabAdapter = new TabAdapter(this, mTabItemBeanArrayList); mTabRecycleView.setAdapter(mTabAdapter); //添加分割线 //设置添加删除动画 //调用ListView的setSelected(!ListView.isSelected())方法,这样就能及时刷新布局 mTabRecycleView.setSelected(true); }else{ mTabAdapter.notifyDataSetChanged(); } //将碎片添加到集合中 fragmentList = new ArrayList<>(); for(int i=0;i<mTabItemBeanArrayList.size();i++){ TabItemBean tabItemBean = mTabItemBeanArrayList.get(i); Bundle bundle = new Bundle(); bundle.putString("param", tabItemBean.getTabUrl()); fragmentList.add(WebViewFragment.getInstance(WebViewFragment.class,bundle)); } //给ViewPager设置适配器 viewPagerAdapter = new MyTabViewPagerAdapter(getSupportFragmentManager(),fragmentList); mViewPager.setAdapter(viewPagerAdapter); } /** * 初始化点击事件 * */ private void initEvent() { //列表适配器的点击监听事件 mTabAdapter.setOnItemClickLitener(new TabAdapter.OnItemClickLitener() { @Override public void onItemClick(View view, int position) { if (mCurrentIndex == position) { return; } setFragmentDisplay(position);//独立出来,用于OnResume的时候初始化展现相应的Fragment savdCheckedIndex = position; mCurrentIndex = position; } }); //viewpager的滑动事件监听 mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override /*此方法是页面跳转完后得到调用,arg0是你当前选中的页面的Position(位置编号)。*/ public void onPageSelected(int arg0) { //解决滑动后,无法点击上一个fragment选项卡的问题 setTabsDisplay(arg0); savdCheckedIndex = arg0; mCurrentIndex = arg0; } @Override /* * 当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法回一直得到调用。其中三个参数的含义分别为: * arg0 :当前页面,及你点击滑动的页面 * arg1:当前页面偏移的百分比 * arg2:当前页面偏移的像素位置 */ public void onPageScrolled(int arg0, float arg1, int arg2) { // TODO Auto-generated method stub } @Override /*此方法是在状态改变的时候调用,其中arg0这个参数有三种状态(0,1,2)。 * arg0==0的时候默示什么都没做 * arg0 ==1的时候默示正在滑动 * arg0==2的时候默示滑动完毕了*/ public void onPageScrollStateChanged(int arg0) { // TODO Auto-generated method stub } }); } /** * 设置导航栏中选项卡之间的切换 */ private void setTabsDisplay(int index){ if(mTabAdapter != null){ mTabAdapter.setSelected(index); mTabRecycleView.smoothScrollToPosition(index);//平移滑动到指定position } } /** * 设置碎片之间的切换 */ private void setFragmentDisplay(int index){ mViewPager.setCurrentItem(index, false);//smoothScroll false表示切换的时候,不经过两个页面的中间页 } /** * http://blog.csdn.net/caesardadi/article/details/20382815 * */ // 自己记录fragment的位置,防止activity被系统回收时,fragment错乱的问题【按home键返回到桌面一段时间,然后在进程里面重新打开,会发现RadioButton的图片选中状态在第二个,但是文字和背景颜色的选中状态在第一个】 //onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。 protected void onSaveInstanceState(Bundle outState) { //http://www.cnblogs.com/chuanstone/p/4672096.html?utm_source=tuicool&utm_medium=referral //总是执行这句代码来调用父类去保存视图层的状态”。其实到这里大家也就明白了,就是因为这句话导致了重影的出现 super.onSaveInstanceState(outState); outState.putInt("selectedCheckedIndex", savdCheckedIndex); outState.putInt("mCurrentIndex", mCurrentIndex); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { savdCheckedIndex = savedInstanceState.getInt("selectedCheckedIndex"); mCurrentIndex = savedInstanceState.getInt("mCurrentIndex"); super.onRestoreInstanceState(savedInstanceState); } }
混淆配置
无
参考资料
暂时空缺