android 自定义控件队列弹出,实现轮转广告带底部指示的自定义ViewPager控件

本文详细介绍了如何通过自定义View实现一个带有底部指示物的ViewPager,包括定义属性、创建PageAdapter、处理布局以及解决在滑动时不显示图片的问题。作者在实现过程中遇到自动切换间隔不准确的疑问,期待解答。
摘要由CSDN通过智能技术生成

有许多博客和开源项目都致力于这项工作,但是他们的工作大都是为了制作类似于启动页的效果,ViewPager全屏显示,或者自己可操作的属性难以满足要求,因此我想把ViewPager和底部的指示物封装在一个自定义的View中,作为一个新的控件在xml中使用,所以自己来实现了一个。

而且,在用自定义视图封装ViewPager时,出现了一个问题,就是ViewPager的所有页不能全部显示的问题,不知道是因为这个问题太简单还是什么其它原因,在网上并没有搜到这个问题的解决方法(事实上连提问的人都没有……),困扰了我半个多星期,终于解决,这一点在正文里会介绍,先来贴一下效果图:

32d186893b0eb683b65f1cd3a629d974.png

下面来介绍我的实现过程:

首先在res/values/目录下创建attrs.xml文件,用来定义新View自定义的属性:

其中:

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 views = null;

private ScaleType scaleType;

public ViewPagerAdapter(List views) {

this(views, ScaleType.CENTER);

}

public ViewPagerAdapter(List 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 dots;

private List 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,并作为参数传进来供ViewPager(PagerAdapter)使用,对外的接口就是这个setViewPagerViews:

public void setViewPagerViews(List 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的布局文件中声明这个组件:

xmlns:daemon="http://schemas.android.com/apk/res/org.daemon.viewpager"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="#666666" >

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" />

然后,在MainActivity中,创建List数组并设置数据:

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

initViewPager();

}

private void initViewPager() {

views = new ArrayList();

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端弹窗管理框架,内部维护弹窗优先级队列 具备弹窗管理扩展功能 整合Dialog,PoupoWindow,悬浮Widget,透明Webview,Toast,SnackBar,无需再为繁琐的业务弹窗逻辑所困扰如何添加依赖只需要两行代码轻松接入//add this to your repositories  maven { url 'https://www.jitpack.io' } //add this to your dependencies implementation 'com.github.MrCodeSniper:PopLayer:2.0.0'具体如何使用1.根据策略创建对应的弹窗view//Dialog形式 PopLayerView  mLayerView = new PopLayerView(this,R.layout.common_dialog_upgrade_app); //透明Webview形式 PopLayerView mLayerView = new PopLayerView(this,LayerConfig.redPocketScheme);2.开始装配弹窗配置Popi mUpgradePopi1 = new Popi.Builder()                 .setmPopId(4)//弹窗的唯一标识 当id发生改变 视为新的弹窗                 .setmPriority(2)//优先级这里不具体划分对应的范围 值越小优先级越高                 .setmCancelType(TRIGGER_CANCEL)//弹窗消失的类型分为 TRIGGER_CANCEL(触摸消失) COUNTDOWN_CANCEL (延时消失)                 .setMaxShowTimeLength(5)//最长显示时间(S)                 .setMaxShowCount(5)//最大显示次数                 .setmBeginDate(1548858028)//开始时间 2019-01-30 22:20:28                 .setmEndDate(1548944428)//结束时间 2019-01-31 22:20:28                 .setLayerView(mLayerView)//弹窗View                 .build();3.纳入弹窗管理//纳入弹窗管理 PopManager.getInstance().pushToQueue(mUpgradePopi); //开始显示弹窗 PopManager.getInstance().showNextPopi();效果预览未来的计划逐步统一 其他类型的弹窗 希望能提供给大家一个较为全面的应对业务需求的弹窗管理框架版本记录V1方案版本号LOG进度更新V1.0.0项目开源,完成弹窗管理与Dialog形式扩展Dialog策略扩展完成V1.0.1修复Dialog策略无法获取dialog实体bugDialog策略优化V1.0.2修复activity摧毁造成的弹窗异常 bugDialog策略优化V1.0.3优化了弹窗的使用更加方便快捷框架使用优化V2方案版本号LOG进度更新V2.0.0正式加入透明Webview弹窗策略扩展透明Webview策略扩展完成作者介绍Hello 我叫lalala,如果您喜欢这个项目 请给个star 能follow我那真是太好了!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值