Android中ViewPager实现滑动指示条及与Fragment的配合
自主实现滑动指示条
先上效果图:
1、xml布局
布局代码如下:
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.testviewpage_2.mainactivity" > <imageview android:id="@+id/cursor" android:layout_width="fill_parent" android:layout_height="wrap_content" android:scaletype="matrix" android:src="@drawable/a" /> <android.support.v4.view.viewpager android:id="@+id/viewpager" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"/> </linearlayout>
采用线性垂直布局,在滑动页面的上方添加一个小水平条。
2、java代码
先给出全部代码,然后再逐步讲解。
public class mainactivity extends activity { private view view1, view2, view3; private list<view> viewlist;// view数组 private viewpager viewpager; // 对应的viewpager private imageview cursor; private int bmpw = 0; // 游标宽度 private int offset = 0;// // 动画图片偏移量 private int currindex = 0;// 当前页卡编号 @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); viewpager = (viewpager) findviewbyid(r.id.viewpager); layoutinflater inflater = getlayoutinflater(); view1 = inflater.inflate(r.layout.layout1, null); view2 = inflater.inflate(r.layout.layout2, null); view3 = inflater.inflate(r.layout.layout3, null); viewlist = new arraylist<view>();// 将要分页显示的view装入数组中 viewlist.add(view1); viewlist.add(view2); viewlist.add(view3); //初始化指示器位置 initcursorpos(); viewpager.setadapter(new mypageradapter(viewlist)); viewpager.setonpagechangelistener(new mypagechangelistener()); } //初始化指示器位置 public void initcursorpos() { // 初始化动画 cursor = (imageview) findviewbyid(r.id.cursor); bmpw = bitmapfactory.decoderesource(getresources(), r.drawable.a) .getwidth();// 获取图片宽度 displaymetrics dm = new displaymetrics(); getwindowmanager().getdefaultdisplay().getmetrics(dm); int screenw = dm.widthpixels;// 获取分辨率宽度 offset = (screenw / viewlist.size() - bmpw) / 2;// 计算偏移量 matrix matrix = new matrix(); matrix.posttranslate(offset, 0); cursor.setimagematrix(matrix);// 设置动画初始位置 } //页面改变监听器 public class mypagechangelistener implements onpagechangelistener { int one = offset * 2 + bmpw;// 页卡1 -> 页卡2 偏移量 int two = one * 2;// 页卡1 -> 页卡3 偏移量 @override public void onpageselected(int arg0) { animation animation = null; switch (arg0) { case 0: if (currindex == 1) { animation = new translateanimation(one, 0, 0, 0); } else if (currindex == 2) { animation = new translateanimation(two, 0, 0, 0); } break; case 1: if (currindex == 0) { animation = new translateanimation(offset, one, 0, 0); } else if (currindex == 2) { animation = new translateanimation(two, one, 0, 0); } break; case 2: if (currindex == 0) { animation = new translateanimation(offset, two, 0, 0); } else if (currindex == 1) { animation = new translateanimation(one, two, 0, 0); } break; } currindex = arg0; animation.setfillafter(true);// true:图片停在动画结束位置 animation.setduration(300); cursor.startanimation(animation); } @override public void onpagescrolled(int arg0, float arg1, int arg2) { } @override public void onpagescrollstatechanged(int arg0) { } } /** * viewpager适配器 */ public class mypageradapter extends pageradapter { public list<view> mlistviews; public mypageradapter(list<view> mlistviews) { this.mlistviews = mlistviews; } @override public boolean isviewfromobject(view arg0, object arg1) { // todo auto-generated method stub return arg0 == arg1; } @override public int getcount() { // todo auto-generated method stub return mlistviews.size(); } @override public void destroyitem(viewgroup container, int position, object object) { // todo auto-generated method stub container.removeview(mlistviews.get(position)); } @override public object instantiateitem(viewgroup container, int position) { // todo auto-generated method stub container.addview(mlistviews.get(position)); return mlistviews.get(position); } } }
从易到难一步步来讲。
1、mypageradapter类
一般我们对于适配器的实现总是new一个pageadapter的实例。我们这里做了一点稍微的更改,将其集合成一个类,内容都没变,只是多了一个构造函数而已。所以针对这个类的具体代码,我就不再细讲,如果对其中的复写的函数为什么要这么写不理解的同学,请看《viewpager 详解(二)---详解四大函数》
2、initcursorpos()---初始化指示器位置
游标在初始化显示时,我们要根据屏幕宽度来显示游标位置。先看看这部分代码:
//初始化指示器位置 public void initcursorpos() { // 初始化动画 cursor = (imageview) findviewbyid(r.id.cursor); bmpw = bitmapfactory.decoderesource(getresources(), r.drawable.a) .getwidth();// 获取图片宽度 displaymetrics dm = new displaymetrics(); getwindowmanager().getdefaultdisplay().getmetrics(dm); int screenw = dm.widthpixels;// 获取分辨率宽度 offset = (screenw / viewlist.size() - bmpw) / 2;// 计算偏移量 matrix matrix = new matrix(); matrix.posttranslate(offset, 0); cursor.setimagematrix(matrix);// 设置动画初始位置 }
可能有些同学不明白的位置在于,初始化位置的偏移量为什么这么算,下面,我画了张图,看下就应该明白了。
最后对于偏移的方法,可用的很多,这里仿网上的代码用了matrix;当然大家可以用其它的偏移方法,一样。
3、mypagechangelistener()---页面改变监听器
代码如下 :
public class mypagechangelistener implements onpagechangelistener { int one = offset * 2 + bmpw;// 页卡1 -> 页卡2 偏移量 int two = one * 2;// 页卡1 -> 页卡3 偏移量 @override public void onpageselected(int arg0) { animation animation = null; switch (arg0) { case 0: if (currindex == 1) { animation = new translateanimation(one, 0, 0, 0); } else if (currindex == 2) { animation = new translateanimation(two, 0, 0, 0); } break; case 1: if (currindex == 0) { animation = new translateanimation(offset, one, 0, 0); } else if (currindex == 2) { animation = new translateanimation(two, one, 0, 0); } break; case 2: if (currindex == 0) { animation = new translateanimation(offset, two, 0, 0); } else if (currindex == 1) { animation = new translateanimation(one, two, 0, 0); } break; } currindex = arg0; animation.setfillafter(true);// true:图片停在动画结束位置 animation.setduration(300); cursor.startanimation(animation); }
原理是这样,根据滑动到的页面,把游标滑动找指定位置。
这里可能有难度的地方在于,数学……
我画了一张图,解释从第一个页面到第二个页面时的距离为什么是“游标宽度+offset*2”,其它距离类似。
这篇就到这了,而且这个难度不太大,讲的可能不太细。
源码中,给大家列出了一个有tab交互的demo,图片如下:
使用fragment实现viewpager滑动
使用fragment实现viewpager滑动是android官方比较推荐的一种做法,我们先再来看一下效果图:
在第一个页面加一个btn
第一页面向第二页面滑动
第二页面向第三个页面滑动
1、适配器实现——fragmentpageradapter
先看完整代码,再细讲:
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(); } }
这里有三个函数,根据第一部分的官方文档,可知,对于fragmentpageradapter的派生类,只重写getitem(int)和getcount()就可以了。
对于构造函数,这里申请了一个fragment的list对象,用于保存用于滑动的fragment对象,并在创造函数中初始化:
public fragadapter(fragmentmanager fm,list<fragment> fragments) { super(fm); // todo auto-generated constructor stub mfragments=fragments; }
然后在getitem(int arg0)中,根据传来的参数arg0,来返回当前要显示的fragment,下面是getitem的官方解释,难度不大,不再细讲。
public abstract fragment getitem (int position)
return the fragment associated with a specified position.
最后,getcount()返回用于滑动的fragment总数;
从构造函数所以看出,我们要构造fragment的集合才行,所以下面我们就先产生我们所需要的fragment类;
2、三个fragment类
第一个fragment类:
xml:(layout1.xml)
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical" > <button android:id="@+id/fragment1_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="show toast" /> </linearlayout>
在其中加入了一个btn
java代码:
public class fragment1 extends fragment { @override public view oncreateview(layoutinflater inflater, viewgroup container, bundle savedinstancestate) { // todo auto-generated method stub view view= inflater.inflate(r.layout.layout1, container, false); //对view中控件的操作方法 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; } }
在oncreateview()中返回要显示的view,上面这段代码简单演示了如何对视图里的控件进行操作,难度不大,不再细讲。第二个fragment类:
xml代码:(layout2.xml)原生代码,没有做任何更改
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffff00" android:orientation="vertical" > </linearlayout>
java代码:
public class fragment2 extends fragment { @override public view oncreateview(layoutinflater inflater, viewgroup container, bundle savedinstancestate) { // todo auto-generated method stub view view=inflater.inflate(r.layout.layout2, container, false); return view; } }
第三个fragment类:
xml代码:(layout3.xml)同样,原生代码,没做任何更改
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ff00ff" android:orientation="vertical" > </linearlayout>
java代码:
public class fragment3 extends fragment { @override public view oncreateview(layoutinflater inflater, viewgroup container, bundle savedinstancestate) { // todo auto-generated method stub view view=inflater.inflate(r.layout.layout3, container, false); return view; } }
3、主activity实现
核心代码:
public class mainactivity extends fragmentactivity { @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.viewpager); vp.setadapter(adapter); } }
首先有一个最值得注意的地方:activity派生自fragmentactivity,其实这是有关fragment的基础知识,只有fragmentactivity才能内嵌fragment页面,普通activity是不行的。
这段代码主要分为两步,第一步:构造适配器;第二步:设定适配器。
先看构造适配器的过程:
//构造适配器 list<fragment> fragments=new arraylist<fragment>(); fragments.add(new fragment1()); fragments.add(new fragment2()); fragments.add(new fragment3()); fragadapter adapter = new fragadapter(getsupportfragmentmanager(), fragments);
构造一个fragment列表,然后将上面的三个fragment类对应的实例添加进去,最后生成fragadapter实例。
至于第二步,设定适配器,没什么好讲的。
4、可能出现的问题
问题:在mainactivity中,当写到这句:fragments.add(new fragment1()); 向fragment列表中添加fragement对象实例时,会提示“无法将fragment1()转换为fragment”
解决办法 :这是因为导入包不一致,一般的问题在于:在fragment1中导入的是android.app.fragment, 而在这里导入类确是:android.support.v4.app.fragment,包不同当然无法转换,统一导入为android.support.v4.app.fragment之后就正常了