《第一行代码》阅读笔记(十四)——ViewPager
关于ViewPager看了一篇文章,虽然非常老了,但是还是很通俗易懂也是一个CSDN的大神。写一些观看后的感受。
1、《ViewPager 详解(一)---基本入门》
2、《ViewPager 详解(二)---详解四大函数》
3、《ViewPager 详解(三)---PagerTabStrip与PagerTitleStrip添加标题栏的异同》
4、《ViewPager 详解(四)----自主实现滑动指示条》
5、《ViewPager 详解(五)-----使用Fragment实现ViewPager滑动》
————————————————
版权声明:本文为CSDN博主「启舰」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/harvic880925/article/details/38453725
ViewPager 详解(一)
此篇博客主要讲述了一个demo,非常的简单。相信大家按照作者描述的进行,问题不大。但这里笔者还是介绍一下自己的理解。因为这个作者的编码风格和《第一行代码》的作者有些出入。
先说一个小Tips,因为博客作者发布文章的时候才2014年,现在已经2020年了,v4已经过时,有的使用v7,而更多的应该是使用androidx了。所以现在自动生成的项目里面是自带ViewPager的,不需要重新导入包。
首先第一步就是添加三个布局,用于滑动。这个不多说,新建即可,为了提高辨识度,修改不同的背景颜色就好了。
第二步就是给主活动布局添加ViewPager,androidx下的ViewPager是这样的
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
</androidx.viewpager.widget.ViewPager>
博客中作者把适配器直接放在主活动里面了,这样非常不好理解,所以笔者在这里把它分开。
第一个文件就是主活动类。
package com.example.tablayoutdemo;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private View view1, view2, view3;
private ViewPager viewPager; //对应的viewPager
private List<View> viewList;//view数组
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.view_pager);
LayoutInflater inflater=getLayoutInflater();
view1 = inflater.inflate(R.layout.layout_1, null);
view2 = inflater.inflate(R.layout.layout_2,null);
view3 = inflater.inflate(R.layout.layout_3, null);
viewList = new ArrayList<View>();// 将要分页显示的View装入数组中
viewList.add(view1);
viewList.add(view2);
viewList.add(view3);
MyFragAdapter adapter = new MyFragAdapter(viewList);
viewPager.setAdapter(adapter);
}
}
LayoutInflater inflater=getLayoutInflater();
view1 = inflater.inflate(R.layout.layout_1, null);
view2 = inflater.inflate(R.layout.layout_2,null);
view3 = inflater.inflate(R.layout.layout_3, null);
这里的代码按照《第一行代码》的风格,应该是这样写的
view1 = LayoutInflater.from(this).inflate(R.layout.layout_1, null);
区别不大,但是感觉getLayoutInflater()使用这个函数是不是好一些呢?因为在平时开发中,笔者经常迷惑于from中的环境到底应该是什么。
参考博客
View.inflate()和LayoutInflater.inflate()的区别
第二个就是适配器。
package com.example.tablayoutdemo;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;
import java.util.List;
public class MyFragAdapter extends PagerAdapter {
private List<View> viewList;
public MyFragAdapter(List<View> viewList) {
this.viewList = viewList;
}
@Override
public int getCount() {
return viewList.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
container.addView(viewList.get(position));
return viewList.get(position);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(viewList.get(position));
}
}
这里看不懂没有关系,会在详解(二)中讲解。
ViewPager 详解(二)
概述
这里斗胆修改了一下中文翻译,如果有错误,还希望各位读者指正。
这个类提供一个适配器用于填充ViewPager页面。你还可以使用一个更加具体的实现接口,例如:FragmentPagerAdapter或者FragmentStatePagerAdapter。
当你实现一个PagerAdapter接口时,至少需要覆盖以下几个方法:
- instantiateItem(ViewGroup, int)
- destroyItem(ViewGroup, int, Object)
- getCount()
- isViewFromObject(View, Object)
PagerAdapter比AdapterView的使用更加普遍。相较于直接提供一个视图回收机制,ViewPager使用回调函数来表示更新的步骤。如果有需要,PagerAdapter也能实现一种形式的视图回收,或者使用一种更为巧妙的方法来管理视图,比如当每个页面被它自己的碎片展示时,使用碎片事务。
ViewPager不直接处理每一个视图而是将各个视图与一个键联系起来。此键用于跟踪和唯一标识给定页面,而不依赖于页面在适配器中的位置。当PagerAdapter调用startUpdate(ViewGroup)函数时表明ViewPager的内容将要发生改变。接下来会调用一次或多次的 instantiateItem(ViewGroup, int)或者destroyItem(ViewGroup, int, Object),在更新完成后会调用finishUpdate(ViewGroup)。在finishUpdate返回的时候,与instantiateItem返回的键对象相关联的视图应该被添加到传递给这些方法的父视图组中,而与传递给destroyItem的键相关联的视图应该被删除。方法isViewFromObject(View, Object)判断力一个页面是否与给定的键相关联。
对于非常简单的PagerAdapter或许你可以选择用页面视图本身作为键,在创建后从instantiateItem返回它们,并将它们添加到父ViewGroup。destroyItem(ViewGroup, int, Object)将会将该视图从父ViewGroup里面移除。isViewFromObject方法里面直接可以返回view == object。
PagerAdapter支持数据集合的改变,数据集合的改变必须要在主线程里面执行,然后还要调用notifyDataSetChanged方法作为结束,这个和源自于BaseAdapter的AdapterView 的适配器非常相似。数据集合的改变包括页面的添加删除和修改位置。如果适配器实现methodgetItemPosition(Object),则ViewPager将保持当前页面处于活动状态。
网址:http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html
isViewFromObject(View, Object)
首先第一个笔者想讲一下这个函数,在第一篇博客中讲到,简单的ViewPager中每个View就是Key,然后会在这个函数中建立关系。一般就是这样
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
getCount()
这个十分简单,就是返回View或者说是滑动界面的个数,一般滑动界面都会被装再一个List里面。这里直接返回数组的长度即可。
@Override
public int getCount() {
return viewList.size();
}
instantiateItem(ViewGroup, int)
简单来说就是加载页面,方法也很简单,通过环境的addView函数,把数组中根据position收到的页面加载到环境中。然后返回View。
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
container.addView(viewList.get(position));
return viewList.get(position);
}
其实这里面返回的就是 isViewFromObject(View, Object)中接收到Object,博客中还提到一个例子,就是返回position。如果这样做的话就需要修改isViewFromObject中的返回值,重新建立对应关系。
自定义key如下:
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
// TODO Auto-generated method stub
//根据传来的key,找到view,判断与传来的参数View arg0是不是同一个视图
return arg0 == viewList.get((int)Integer.parseInt(arg1.toString()));
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
// TODO Auto-generated method stub
container.addView(viewList.get(position));
//把当前新增视图的位置(position)作为Key传过去
return position;
}
感觉好像没有什么必要,因为在文档中也说明了,简单的适配器可以直接使用View作为Key,可能在后续的复杂环境中,需要对视图的对应关系做出调整。
destroyItem(ViewGroup, int, Object)
和添加几乎差不多,就是删除,然后没有返回值。
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(viewList.get(position));
}
ViewPager 详解(三)
此篇博客里面讲的基本上都不怎么用了,大家可以不看。现在都是用功能更强大的TabLayout。
ViewPager 详解(四)
此篇博客主要是对标题滑动模块的原理进行讲解,对源码感兴趣的朋友们,可以看一下。这里就不做赘述了,原因是一样的,现在都是用功能更强大的TabLayout。
ViewPager 详解(五)
概述
FragmentPagerAdapter派生自PagerAdapter,它将每个页面表示为一个Fragment,只要用户能够返回到该页面,这个Fragment就会一直保存在碎片管理器中。
PagerAdapter最好用于有限个静态碎片页面的管理,例如一系列标签。尽管不可见的视图有时会被销毁,但用户所有访问过的碎片都会被保存在内存中,这导致碎片实例会保存大量的各种状态,造成了很大的内存开销。所以如果要处理大量的页面切换,建议使用FragmentStatePagerAdapter。
每一个使用FragmentPagerAdapter的ViewPager都要有一个有效的ID集合(有效ID的集合就是Fragment的集合)。
对于FragmentPagerAdapter的派生类,只需要重写getItem(int)和getCount()就可以了。
实现
其实不使用碎片技术也是可以在ViewPager中的每一页上面添加各式各样的布局,但是不能绑定事件。所以就要引入碎片技术。
第一步:修改页面布局
在原来的页面布局的基础上。给layout_1.xml添加一个按钮,其他的不变。
第二步:给每个碎片添加一个类,进行绑定。非常简单就是加载布局,然后可以给layout_1.xml绑定一个点击事件
package com.example.tablayoutdemo;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class Fragment1 extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_1, container, false);
Button btn = (Button) view.findViewById(R.id.fragment1_btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Toast.makeText(getActivity(), "点击了第一个fragment的BTN", Toast.LENGTH_SHORT).show();
}
});
return view;
}
}
其实这里我有个问题,就是为啥Toast中是getActivity()。这样的话获得的就是一个Activity了,而之前传递到参数都是Activity.this。就这些笔者确实还有些模糊。
——Activity就是环境
第三步:编写适配器
因为需要和碎片联立,所以不能使用之前的适配器了,需要使用FragmentPagerAdapter。代码如下
package com.example.tablayoutdemo;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import java.util.List;
public class FragAdapter extends FragmentPagerAdapter {
private List<Fragment> mFragments;
public FragAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
// TODO Auto-generated constructor stub
mFragments=fragments;
}
@Override
public Fragment getItem(int arg0) {
// TODO Auto-generated method stub
return mFragments.get(arg0);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mFragments.size();
}
}
只需要重写getItem和getCount,非常简单直白,就不多说了。
第四步:主活动类
package com.example.tablayoutdemo;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//构造适配器
List<Fragment> fragments = new ArrayList<Fragment>();
fragments.add(new Fragment1());
fragments.add(new Fragment2());
fragments.add(new Fragment3());
FragAdapter adapter = new FragAdapter(getSupportFragmentManager(), fragments);
//设定适配器
ViewPager vp = (ViewPager) findViewById(R.id.view_pager);
vp.setAdapter(adapter);
}
}
非常的简单,就是实例化不同的Fragment之后在放置在list中,将list和碎片管理器一起传入适配器中。通过ID寻找ViewPager再设置adapter。
至此ViewPager就算告一段落了。