【Android】带底部指示的自定义ViewPager控件
在项目中经常需要使用轮转广告的效果,在android-v4版本中提供的ViewPager是一个很好的工具,而一般我们使用Viewpager的时候,都会选择在底部有一排指示物指示当前显示的是哪一个page,这么常用的组合如果每次用都重头写当然是一件很麻烦的事情,有许多博客和开源项目都致力于这项工作,但是他们的工作大都是为了制作类似于启动页的效果,ViewPager全屏显示,或者自己可操作的属性难以满足要求,因此我想把ViewPager和底部的指示物封装在一个自定义的View中,作为一个新的控件在xml中使用,所以自己来实现了一个。
而且,在用自定义视图封装ViewPager时,出现了一个问题,就是ViewPager的所有页不能全部显示的问题,不知道是因为这个问题太简单还是什么其它原因,在网上并没有搜到这个问题的解决方法(事实上连提问的人都没有……),困扰了我半个多星期,终于解决,这一点在正文里会介绍,先来贴一下效果图:
下面来介绍我的实现过程:
首先在res/values/目录下创建attrs.xml文件,用来定义新View自定义的属性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyViewPager">
<attr name="dotsViewHeight" format="dimension" />
<attr name="dotsSpacing" format="dimension" />
<attr name="dotsFocusImage" format="reference" />
<attr name="dotsBlurImage" format="reference" />
<attr name="android:scaleType" />
<attr name="android:gravity" />
<attr name="dotsBackground" format="reference|color" />
<attr name="dotsBgAlpha" format="float" />
<attr name="changeInterval" format="integer" />
</declare-styleable>
</resources>
其中:
dotsViewHeight定义底部指示物所在视图(我定义为一个LinearLayout)的高度,也就是示例图中圆圈所在灰色透明部分的高度,默认为40像素;
dotsSpacing定义底部指示物之间的间距,默认为0;
dotsFocusImage定义代表当前页的指示物的样子;
dotsBlurImage定义代表非当前页的指示物的样子;
android:scaleType定义ViewPager中ImageView的scale类型,如果ViewPager中的View不是ImageView,则此属性没有效果,默认为ScaleType.FIT_XY;
android:gravity定义底部指示物在父View(即示例灰色透明部分)的gravity属性;
dotsBackground定义底部指示物的背景颜色或背景图;
dotsBgAlpha定义底部指示物的背景颜色或背景图的透明度,取值为0-1,0代表透明;
changeInteval定义ViewPager自动切换的时间间隔,单位为ms,默认为1000ms(这个地方实际的间隔比设置的要大,不知道是什么原因,望高手解答);
下一步,定义PageAdapter,为ViewPager提供内容:
public class ViewPagerAdapter extends PagerAdapter { private List<View> views = null;
private ScaleType scaleType; public ViewPagerAdapter(List<View> views) {
this(views, ScaleType.CENTER);
} public ViewPagerAdapter(List<View> views, ScaleType scaleType) {
super();
this.views = views;
this.scaleType = scaleType;
}
定义一个views来存储要显示的View,然后定义一个ScaleType来规定如果ViewPager是用来显示ImageView的,ImageView应该怎样呈现在ViewPager当中,如果调用的构造函数不传ScaleType信息,则默认使用ScaleType.CENTER。
根据官方API描述,需要重写PageAdapter的getCount,isViewFromObject,instantiateItem和destroyItem这四个方法,在instantiateItem中设置ScaleType,其它几个方法,都是用官方描述的写法,没有做什么新的改动:
@Override
public int getCount() {
// TODO Auto-generated method stub
return views.size();
} @Override
public boolean isViewFromObject(View arg0, Object arg1) {
// TODO Auto-generated method stub
return arg0 == arg1;
} @Override
public Object instantiateItem(View container, int position) {
// TODO Auto-generated method stub
View view = views.get(position);
ViewPager viewPager = (ViewPager) container;
if (view instanceof ImageView){
((ImageView) view).setScaleType(scaleType);
}
viewPager.addView(view, 0);
return view;
} @Override
public void destroyItem(View container, int position, Object object) {
// TODO Auto-generated method stub
((ViewPager) container).removeView((View) object);
}
下面就是重头戏了,核心类,被封装的底部带指示物的ViewPager,基本思路是自定义一个类继承LinearLayout,在里面加入两个子视图ViewPager和LinearLayout(放置指示物),并且,因为要定期轮转,还实现了Runnable接口,定义了以下的变量:
public class MyViewPager extends LinearLayout implements Runnable { private ViewPager viewPager;
private LinearLayout viewDots;
private List<ImageView> dots;
private List<View> views; private int position = 0;
private boolean isContinue = true; private float dotsViewHeight;
private float dotsSpacing;
private Drawable dotsFocusImage;
private Drawable dotsBlurImage;
private ScaleType scaleType;
private int gravity;
private Drawable dotsBackground;
private float dotsBgAlpha;
private int changeInterval;
viewPager是要显示的ViewPager对象,viewDots是放置指示物的子视图,dots是viewDots上的指示物项,views是ViewPager项,position指示当前正在显示第几张图,isContinue表示可不可以自动轮转(当手指触摸时不轮转),在下面的就是雨attrs.xml中定义的属性相对应的值。作为一个能够在xml布局文件中直接使用的View,必须重写拥有Context和AttributeSet参数的构造函数:
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.MyViewPager, 0, 0);
try {
dotsViewHeight = a.getDimension(
R.styleable.MyViewPager_dotsViewHeight, 40);
//这里依次获取所有的属性值,此处省略,可参看最后附上的全部代码
} finally {
a.recycle();
} initView();
}
最后调用的函数initView,用来初始化ViewPager和LinearLayout这两个子视图,同时,如果xml中给指示物设置了背景,在这里进行设置:
@SuppressLint("NewApi")
private void initView() {
// TODO Auto-generated method stub
viewPager = new ViewPager(getContext());
viewDots = new LinearLayout(getContext()); LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
addView(viewPager, lp);
if (dotsBackground != null) {
dotsBackground.setAlpha((int) (dotsBgAlpha * 255));
viewDots.setBackground(dotsBackground);
}
viewDots.setGravity(gravity);
addView(viewDots, lp);
}
使用这个类时,关键就是创建一个List<View>,并作为参数传进来供ViewPager(PagerAdapter)使用,对外的接口就是这个setViewPagerViews:
public void setViewPagerViews(List<View> views) {
this.views = views;
addDots(views.size()); viewPager.setAdapter(new ViewPagerAdapter(views, scaleType)); viewPager.setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int index) {
// TODO Auto-generated method stub
position = index;
switchToDot(index);
}
//override的两个空方法,此处省略
}); viewPager.setOnTouchListener(new OnTouchListener() { @Override
public boolean onTouch(View view, MotionEvent motionevent) {
// TODO Auto-generated method stub
switch (motionevent.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
isContinue = false;
break;
case MotionEvent.ACTION_UP:
isContinue = true;
break;
default:
isContinue = true;
break;
}
return false;
}
});
new Thread(this).start();
}
addDots就是在底部添加多少个小点,默认第一个处于被选中状态,关键是OnPageChangeListener的onPageSelected方法,这个方法在viewPager进行切换时调用,做的工作就是把底部的指示物切换到对应的标识上,在这个方法的最后,启动了轮转的线程。
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
if (isContinue) {
pageHandler.sendEmptyMessage(position);
position = (position + 1) % views.size();
try {
Thread.sleep(changeInterval);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} Handler pageHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
viewPager.setCurrentItem(msg.what);
super.handleMessage(msg);
}
};
在这个线程中,每隔固定秒数,就向Handler队列中发送一个消息,内容就是要显示的view项的index,然后再handler中调用viewPager的setCurrentItem方法进行跳转。至此,最核心的类就完成了,但还剩很关键的一个方法,作为一个自定义的View,要重写父类的onLayout方法来对子元素进行布局,就是这一个方法中不当的代码,导致每次只能显示前两张图,因为ViewPager在显示时,会默认初始化当前页和前后页,对于第一张来说,没有前一页,所以初始化了两张,在ViewPager滑动时,每次都会调用onLayout方法,而且,changed参数为false,我已开始只判断changed为true时才进行布局,就造成了上述问题,完整的onLayout代码如下:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
View child = this.getChildAt(0);
child.layout(0, 0, getWidth(), getHeight()); if (changed) {
child = this.getChildAt(1);
child.measure(r - l, (int) dotsViewHeight);
child.layout(0, getHeight() - (int) dotsViewHeight, getWidth(),
getHeight());
}
}
最后,就是如何使用这个类了,首先,在activity的布局文件中声明这个组件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:daemon="http://schemas.android.com/apk/res/org.daemon.viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#666666" > <org.daemon.viewpager.MyViewPager
android:id="@+id/my_view_pager"
android:layout_width="match_parent"
android:layout_height="200dp"
daemon:dotsViewHeight="30dp"
daemon:dotsFocusImage="@drawable/dot_focused"
daemon:dotsBlurImage="@drawable/dot_normal"
daemon:dotsSpacing="5dp"
daemon:dotsBackground="#999999"
daemon:dotsBgAlpha="0.5"
daemon:changeInterval="3000"
android:scaleType="fitXY"
android:gravity="center" /> </RelativeLayout>
然后,在MainActivity中,创建List<View>数组并设置数据:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViewPager();
} private void initViewPager() {
views = new ArrayList<View>(); ImageView image = new ImageView(this);
image.setImageResource(R.drawable.demo_scroll_image);
views.add(image);
image = new ImageView(this);
image.setImageResource(R.drawable.demo_scroll_image2);
views.add(image);
image = new ImageView(this);
image.setImageResource(R.drawable.demo_coupon_image);
views.add(image);
image = new ImageView(this);
image.setImageResource(R.drawable.demo_scroll_image2);
views.add(image); MyViewPager pager = (MyViewPager) findViewById(R.id.my_view_pager);
pager.setViewPagerViews(views);
}
至此,本示例就全部讲解完了,两个问题,一个就是为什么使用Thread的方法来控制时间间隔,实际值会比设置的值长,是因为Message在排队吗,第二个问题,就是为什么ViewPager滑动时不重新对ViewPager布局,就会不显示任何图,这两个问题还有待大家解答。
【Android】带底部指示的自定义ViewPager控件的更多相关文章
- Android实现图片轮显效果——自定义ViewPager控件
一.问题概述 使用ViewPager控件实现可横向翻页.水平切换图片等效果,但ViewPager需要手动滑动才能切换页面,图片轮显效果的效果本质上就是在ViewPager控件的基础上让它能自动的进行切 ...
- Android学习(十七)自定义View控件 TopBar
一.创建自定义TopBar头部菜单条 实现步骤: 1.在values中添加attrs.xml文件,设置自定义属性. 2.添加Topbar类,继承RelativeLayout,实现具体功能. 3.添加到 ...
- Android开发(二)——自定义圆形控件的使用CircleImageView
CircleImageView,a fast circular ImageView perfect for profile images. 主要的类CircleImageView: package d ...
- Android View体系(十)自定义组合控件
相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源 ...
- Android高手进阶教程(二十八)之---Android ViewPager控件的使用(基于ViewPager的横向相册)!!!
分类: Android高手进阶 Android基础教程 2012-09-14 18:10 29759人阅读 评论(35) 收藏 举报 android相册layoutobjectclassloade ...
- 【转】带checkbox的ListView实现(二)——自定义Checkable控件的实现方法
原文网址:http://blog.csdn.net/harvic880925/article/details/40475367 前言:前一篇文章给大家展示了传统的Listview的写法,但有的时候我们 ...
- android之视频播放系统VideoView和自定义VideoView控件的应用
Android播放视频,包含系统自带VideoView控件,和自定义VideoView控件,可全屏播放,案例包含了本地视频和网络视频. 1:自定义VideoView控件 2:布局代码 3:Activi ...
- 【Android开发日记】之入门篇(十四)——Button控件+自定义Button控件
好久不见,又是一个新的学期开始了,为什么我感觉好惆怅啊!这一周也发生了不少事情,节假日放了三天的假(好久没有这么悠闲过了),实习公司那边被组长半强制性的要求去解决一个后台登陆的问题,结果就是把 ...
- Android自定义控件之自定义组合控件
前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发 ...
随机推荐
- 【字符集及字符编码】UTF-8、UTF-16和UTF-32
UTF-32 用 4 个字节存储每一个字符,以保证能把 UCS 完全表达出来.但实际上 UCS 的字符数量根本不需要用 32 位表示,UTF-32 极大地浪费了空间.另外,由于组合字符的存在,定长表示 ...
- 树莓派个人实测 Q&A(最新修改使用Manjaro连接远程桌面) (二)
以下内容使用和http://www.eeboard.com/bbs/thread-5191-1-1.html所在的帖子一样的风格,不过原作者是window下的操作,本人的都是在manjaro linu ...
- Oracle创建自增长主键
Oracle主键常用的分为UUID和自增长int两种,下面简单说下各自的优缺点: UUID的优点 1.生成方便,不管是通过sys_guid() 还是java的uuid都能很方便的创建UUID. 2.适 ...
- ansible 2.7.1 快速开始
refer to 官方手册 https://docs.ansible.com/ansible/latest/modules/modules_by_category.html refer to 中文手册 ...
- ActiveMQ 翻译第一章 1.2小节(松耦合与ActiveMQ和何时使用ActiveMQ)
第一章 1.2.1小节 松耦合与ActiveMQ ActiveMQ为应用程序架构提供送耦合实现组件.松耦合经常被引入到系统架构中,来减轻紧耦合的远程工程调用的使用.松耦合的设计是异步的,来自其他系统 ...
- Codeforces 732F. Tourist Reform (Tarjan缩点)
题目链接:http://codeforces.com/problemset/problem/732/F 题意: 给出一个有n个点m条边的无向图,保证联通,现在要求将所有边给定一个方向使其变成有向图,设 ...
- perl learning
Perl 中文教程 http://cn.perlmaven.com/perl-tutorial learning perl in about 2 hours 30 minutes http://qnt ...
- Autolayout 02
Working with Auto Layout Programmatically 如果你在运行阶段添加或者移除views你就需要通过代码来添加约束来保证你的interface能正确适应size或者o ...
- 快速销售订单 - OM:销售订单表单:级联行题头更改
PROFILE OM:销售订单表单:级联行题头更改
- ThinkPHP示例:CURD
完整的控制器文件: class IndexAction extends Action { // 查询数据 public function index() { $Form = M("Form& ...