优雅自定义轮播图
来源:互联网 发布:软件无线电技术与实现 编辑:程序博客网 时间:2024/05/24 06:05
开篇
这个是好久前项目中自己写的,实现了一组图片的轮播展示效果,解脱第三方,打造属于自己的轮播组件。
效果预期
概要设计轮播容器及注意事项
1.BannerView只要有两部分部分组成,一部分是ViewPager,另一部分是指向器。那么我们只需要向容器添加ViewPager和特定的指向器容器即可,然后将二者建立连接,达到我们轮播图说要的效果。
2.BannerView一般都需要做得到尺寸的适配,为了优化我们BannerView的性能,我们可以修改我们的onMeasure()方法和onLayout()方法,通过等比例缩修改我们BannerView的尺寸来做到任何机型都能完美显示
3.BannerView在通过Handler来处理动态切换的时候,我们需要预防内训泄露,在Activity的onResume()中开启我们的播放,在onStoop()中停止我们的播放。否则很有可能会造成内存泄露,切记。
编码阶段
(一).构造方法,这里我们将从xml文件中得到我们自定义的属性,当然这些属性我们也可以在java代码中动态设置。
public BannerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //拿到自定义属性的容器 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.BannerView); //指向器的半径 point_size = (int) array.getDimension(R.styleable.BannerView_point_size, 15); //指向器的颜色 //point_bg = array.getColor(R.styleable.BannerView_point_bg, 0x00000000); point_bg = Color.parseColor("#00000000"); //指向器是否在BannerView的右侧(居中和右侧两种情况) isRight = array.getBoolean(R.styleable.BannerView_is_right, false); //是否需要自动播放 isPlay = array.getBoolean(R.styleable.BannerView_is_play, true); //当没有数据的时候,是否需要提供一张默认图,否则我们将会隐藏掉 isNeedStaticLocaltion = array.getBoolean(R.styleable.BannerView_is_static_location, true); //指向器未选中的颜色 nomalColor = array.getColor(R.styleable.BannerView_point_color_unselect, Color.WHITE); //指向器选中的颜色 selectColor = array.getColor(R.styleable.BannerView_point_color_select, Color.BLACK); //BannerView的高:宽的比例 proportion = array.getFloat(R.styleable.BannerView_banner_proportion, 0.6f); //回收资源 array.recycle(); }
(二).BannerView的初始化入口是setConfig()方法,这个方法在随着外界用户提供的数据会向BannerView中添加ViewPager和指向器容器,我们慢慢看来。
public void setConfig(List<T> pictureList, boolean needStaticLocaltion, boolean isPlay, UserControal<T> userControal) { //这里一定要先移除所有的View,有可能会外界用户会调用多次的setConfig方法 removeAllViews(); this.pictureList = pictureList; isNeedStaticLocaltion = needStaticLocaltion; this.isPlay = isPlay; this.userControal = userControal; //初始化BannerView init(); //初始化指向器 initPoint(); //初始化vp initVp(); //最后会指向autoPlay autoPlay(); }
1.初始化BannerView
/** * 添加ViewPager和指向器布局 */ private void init() { vp = new ViewPager(getContext()); pointLayout = new RelativeLayout(getContext()); addView(vp); addView(pointLayout); RelativeLayout.LayoutParams lpVp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); RelativeLayout.LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); lp.rightMargin = 38; pointLayout.setBackgroundColor(point_bg); vp.setLayoutParams(lpVp); pointLayout.setLayoutParams(lp); }
2.初始化指向器
/** * 生成圆点指示器 */ public void initPoint() { if (null == pictureList || pictureList.size() < 2) { return; } LinearLayout layout = new LinearLayout(getContext()); int size = pictureList.size(); layout.setOrientation(LinearLayout.HORIZONTAL); myCilcleViews = new ArrayList<>(); for (int i = 0; i < size; i++) { MyCilcleView cilcleView = new MyCilcleView(getContext()); layout.addView(cilcleView); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(point_size, point_size); lp.setMargins(MARGIN, 0, MARGIN, 0); cilcleView.setLayoutParams(lp); if (i == 0) { cilcleView.drawPoint(selectColor, point_size); } else { cilcleView.drawPoint(nomalColor, point_size); } myCilcleViews.add(cilcleView); } RelativeLayout.LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); pointLayout.addView(layout, lp); }
3.圆点View
/** * point,画出我们想要的实行园 */ public class MyCilcleView extends TextView { private int mColor = Color.BLACK; private int mRadius = 7; public MyCilcleView(Context context) { super(context); } public MyCilcleView(Context context, AttributeSet attrs) { super(context, attrs); } public MyCilcleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint p = new Paint(); p.setColor(mColor); //画笔是否实心 p.setStyle(Paint.Style.FILL); //抗锯齿 p.setAntiAlias(true); int mineRadius = Math.min(getWidth() / 2, getHeight() / 2); if (mRadius > mineRadius) { mRadius = mineRadius; } canvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, p); } public void drawPoint(int mColor, int mRadius) { this.mColor = mColor; this.mRadius = mRadius; invalidate(); } }
4.初始化ViewPager,大致思路就是根据外界传递的数据动态添加图片,值得注意的一点就是,我们当滑动到最后一张时,如何平稳的滑动到第一张,那么我们可以尝试这么做,如果我们需要展示5张图片,那么我们需要添加6个ImageView,第六个和第一个展示的图片是一样的,当滑动到底六张时我们就让当前的currentPosition为0,这样就解决了由最后一张滑动到第一张跳动的bug。当然还要处理我们触摸时间,当我们触摸在ViewPager上的时候我们的ViewPager是不可以自动播放的。
private void initVp() { imageViews = new ArrayList<>(); if (pictureList == null || pictureList.size() == 0) { if (isNeedStaticLocaltion) { ImageView img = new ImageView(getContext()); img.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); img.setImageResource(mDefaultImg); img.setScaleType(ImageView.ScaleType.CENTER_CROP); imageViews.add(img); initImg(0); } else { setVisibility(GONE); return; } } else { int size = pictureList == null ? 0 : pictureList.size() + 1; if (pictureList != null && pictureList.size() == 1) { size = 1; } for (int i = 0; i < size; i++) { ImageView img = new ImageView(getContext()); img.setImageResource(mDefaultImg); img.setScaleType(ImageView.ScaleType.CENTER_CROP); img.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); imageViews.add(img); initImg(i); } } BannerAdapter adapter = new BannerAdapter(); vp.setAdapter(adapter); vp.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { if (pictureList == null || pictureList.size() <= 1) { return; } if (position == pictureList.size()) { vp.setCurrentItem(0, false); myCilcleViews.get(0).drawPoint(selectColor, point_size); myCilcleViews.get(position - 1).drawPoint(nomalColor, point_size); currentPosition = 0; return; } myCilcleViews.get(currentPosition).drawPoint(nomalColor, point_size); myCilcleViews.get(position).drawPoint(selectColor, point_size); currentPosition = position; } @Override public void onPageScrollStateChanged(int state) { } }); vp.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: currentX = event.getX(); currentY = event.getY(); stop(); break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: if (Math.max(Math.abs(currentX - event.getX()), Math.abs(currentY - event.getY())) > MINE_XY) { //属于滑动了 autoPlay(); } else { if (userControal != null) { userControal.pageClick(currentPosition, t); } } break; } return false; } }); }
5.自动轮播
/** * 暴露的方法,开始轮播 */ public void autoPlay() { if (pictureList == null) { return; } if (!isPlay && pictureList.size() > 1) { return; } hander.sendEmptyMessageDelayed(FLAG, 3000); }
private Handler hander = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (null != msg) { if (msg.what == FLAG) { //移除掉消息 hander.removeMessages(FLAG); vp.setCurrentItem(currentPosition + 1); hander.sendEmptyMessageDelayed(FLAG, 3000); } } } };
6.更新BannerView组件
public void notifyBanner(List<T> pictureList) { removeAllViews(); this.pictureList = pictureList; init(); initPoint(); initVp(); autoPlay(); }
7.测量和layout
//这里我们将根据proportion,来得到我们的高度,然后makeMeasureSpec()方法来生成我们适配的测量数据,最后调用super方法,我们的model设置为MeasureSpec.EXACTLY,这样提升了我们的性能,然后又不会出错。 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int w = MeasureSpec.getSize(widthMeasureSpec); int h = (int) (w * proportion); int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (i == 0 && child instanceof ViewPager) { int hM = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY); child.measure(widthMeasureSpec, hM); } else { int hM = MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST); child.measure(widthMeasureSpec, hM); } } //setMeasuredDimension(w, h); int MW = MeasureSpec.makeMeasureSpec(w,MeasureSpec.EXACTLY); int MH = MeasureSpec.makeMeasureSpec(h,MeasureSpec.EXACTLY); super.onMeasure(MW, MH); }
//这里指的注意的是,我们的指向器是有两中选择的,这里就需要我们的onLayout方法来完成这个效果,把指向器摆放在指定的位置。 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); int count = getChildCount(); for (int i = 0; i < count; i++) { View v = getChildAt(i); if (v == vp) { v.layout(0, 0, getMeasuredWidth(), getMeasuredHeight()); } if (v == pointLayout) { pointLayout.layout(0, getMeasuredHeight() - pointLayout.getMeasuredHeight() - 30, getMeasuredWidth(), getMeasuredHeight()); int c = pointLayout.getChildCount(); if (c == 1) { View view = pointLayout.getChildAt(0); int w = pointLayout.getMeasuredWidth(); int ww = view.getMeasuredWidth(); int centerW = (w - ww) / 2; int h = pointLayout.getMeasuredHeight(); int hh = view.getMeasuredHeight(); int centerH = (h - hh) / 2; if (!isRight) { view.layout(centerW, centerH, centerW + ww, centerH + hh); } else { view.layout(w - ww - 30, centerH, w - 30, centerH + hh); } } } } }
8.回调接口
public interface UserControal<T> { void initPicture(ImageView img, T t, int position); void pageClick(int position, T t); } //这个跟回调关系巨大,我们将图片的加载交由用户处理 private void initImg(int position) { if (pictureList == null || pictureList.size() == 0) { imageViews.get(position).setImageResource(mDefaultImg); return; } if (userControal != null) { if (position < pictureList.size()) { //imageViews的个数比pircture的数量多1 userControal.initPicture(imageViews.get(position), pictureList.get(position), position); } else { userControal.initPicture(imageViews.get(position), pictureList.get(0), 0); } } }
总结
这里,就是大部分源码了,优雅实现了适配任何机型的轮播View,由于特殊原因无法上传github,有需要的说一声。
1 0
- 优雅自定义轮播图
- 优雅的自定义TabLayout
- 优雅自定义Dialog
- AngularJS 优雅的自定义指令
- Android自定义控件系列(六)—优雅的实现广告轮播图
- 如何自定义一个优雅的ContentProvider
- Alert ActionSheet 优雅的可自定义
- 优雅自定义请求&servlet方法调用一体化
- Android中如何优雅地自定义一个View
- 自定义控件从xml获取属性值的优雅写法
- Django template 中优雅的自定义权限过滤filter
- 利用ScriptableObject在U3D里优雅地实现自定义配置文件
- 结合自定义Notification优雅的实现消息透传
- 以优雅的方式在sqlite数据库中创建自定义SQL函数
- Android 自定义控件 优雅实现元素间的分割线 (支持3.0以下)
- Android 自定义控件 优雅实现元素间的分割线 (支持3.0以下)
- Android 自定义控件 优雅实现元素间的分割线 (支持3.0以下)
- Android 自定义控件 优雅实现元素间的分割线 (支持3.0以下)
- 使用AsyncTask自定义图片加载类
- Rxjava2源码分析(一):Flowable的创建和基本使用过程分析
- 利用HashSet对数组去重
- 泊松融合(Poisson blend)
- android 自定义相机Camera2
- 优雅自定义轮播图
- MyEclipse反编译Class文件的实现
- 关于cloudstack中遇见的一些问题处理笔记
- java获取文件大小
- 考试篇(5.2) NSE4 题库 17. 单点登录 ❀ 飞塔 (Fortinet) 网络安全专家
- Java 并发专题 :闭锁 CountDownLatch 之一家人一起吃个饭
- 第一步 javaweb开发工具说明
- 底部弹出DialogFragment中使用ViewPager,ViewPager中使用Fragment出现Fragment does not have a view错误
- java.lang.IllegalStateException: The content of the adapter has changed but