android 轮番图——viewpager实现(自动轮番,手动轮番);bug汇总以及解决
来源:互联网 发布:平板电脑无法加入网络 编辑:程序博客网 时间:2024/05/17 08:22
之前用过三方的轮番图工具库,总想着自己来实现一个,毕竟自己写的东西自己最好控制。
第一个想到的是使用viewpager去实现。看了网上其他人的例子,多多少少会有这样那样的问题,这里我结合他人的例子中的问题写了一个可以完美使用的viewpager;(目前我已经将项目中的轮番图换成我自己写的了)
首先说一下这个轮番图的功能:
1:自动轮番
2:手动轮番,自动轮番停止
3:动态添加和移除数据
4:点击轮番图有点击事件
再说说之前遇到的问题:
1:数据为null的处理
2:数据为1的时候禁止轮番
3:数据为2或者3的时候轮番图出现空白的处理
4:父view为viewpager的时候解决滑动冲突
5,动态添加数据和移除数据的时候出现空白的处理
如果你需要的轮番图是这样的需求,如果你在做轮番图的时候也遇到了类似的问题,那么你可以继续往下看。
效果图就不放了,轮番图想必大家都看过
1:项目结构图
其中:
MyAdapter 为viewpager的适配器
Contant 为存放的常量
DisplayUtil 为转换dp与xp的工具类
FixedSpeedScroller 为重写viewpager的scroller的监听类
TakeTurnsView 为轮番图主类
NoScrollViewPager 为重写的viewpager类
2:Contant 定义常量
public class Contant { public static boolean isDown;//是否点击了轮番图,默认false public static boolean isRun;//是否在运行,默认false}
3:NoScrollViewPager 控制viewpager是否滑动
/** * 传入onScroll 控制当前 viewpager */public class NoScrollViewPager extends ViewPager { private boolean noScroll = false;//false可以滑动;true则不能滑动 private Handler handler;//自动轮番的消息机制 public NoScrollViewPager(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } public NoScrollViewPager(Context context) { super(context); } public void setNoScroll(boolean noScroll) { this.noScroll = noScroll; } @Override public void scrollTo(int x, int y) { super.scrollTo(x, y); } @Override public boolean onTouchEvent(MotionEvent arg0) { //如果不能轮番,则直接将touch事件向上传递 if (noScroll) return false; else return super.onTouchEvent(arg0); } @Override public boolean onInterceptTouchEvent(MotionEvent arg0) { //如果不能轮番,则直接将touch事件向上传递 if (noScroll) return false; else return super.onInterceptTouchEvent(arg0); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { //如果不能轮番,则自动轮番的消息不再发送 if (noScroll) return super.dispatchTouchEvent(ev); int action = ev.getAction(); if (action == MotionEvent.ACTION_DOWN) { Contant.isRun = false;//手指按下,则不能自动轮番 Contant.isDown = true;//按下状态为true if (handler != null) handler.removeCallbacksAndMessages(null);//清空消息队列 } else if (action == MotionEvent.ACTION_UP) { Contant.isRun = true;//手指抬起,自动轮番开始 Contant.isDown = false;//按下状态为false if (handler != null) { handler.removeCallbacksAndMessages(null);//清空消息队列 handler.sendEmptyMessage(1);//重新发送轮番的指令 } } return super.dispatchTouchEvent(ev); } public void setInfinateAdapter(Handler handler, MyAdapter adapter) { this.handler = handler; setAdapter(adapter); } @Override public void setCurrentItem(int item, boolean smoothScroll) { super.setCurrentItem(item, smoothScroll); } @Override public void setCurrentItem(int item) { super.setCurrentItem(item); }}
4,FixedSpeedScroller 重新设置viewpager的轮番时间
/** * Created by 浩 on 2016/8/31. * 重写scroller来控制viewpager的滑动速度 */public class FixedSpeedScroller extends Scroller { private int mDuration = 400;//默认的轮番时间为400ms public FixedSpeedScroller(Context context) { super(context); } public FixedSpeedScroller(Context context, Interpolator interpolator) { super(context, interpolator); } @Override public void startScroll(int startX, int startY, int dx, int dy, int duration) { // Ignore received duration, use fixed one instead super.startScroll(startX, startY, dx, dy, mDuration); } @Override public void startScroll(int startX, int startY, int dx, int dy) { // Ignore received duration, use fixed one instead super.startScroll(startX, startY, dx, dy, mDuration); } /**外界赋值轮番时间 * @param time */ public void setmDuration(int time) { mDuration = time; } public int getmDuration() { return mDuration; }}
5,MyAdapter 适配器
/** * Created by 浩 on 2016/8/30. * 处理viewpager的轮番适配器 */public class MyAdapter extends PagerAdapter { private List<ImageView> mImageViews; public void setmImageViews(List<ImageView> mImageViews) { this.mImageViews = mImageViews; } public void closeData() { mImageViews.clear(); } @Override public int getCount() { return Integer.MAX_VALUE; } @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0 == arg1; } /** * 移除未显示出来的item * * @param container * @param position * @param object */ @Override public void destroyItem(ViewGroup container, int position, Object object) { if (mImageViews==null||mImageViews.size() < 1) return; container.removeView(mImageViews.get(position % mImageViews.size())); } /** * 添加每一个item * 最重要的算法自行理解,比较简单 */ @Override public Object instantiateItem(ViewGroup container, int position) { if (mImageViews==null||mImageViews.size() < 1) return null; try { //有时候添加的视图会未没上个父view移除,所以这里抛出异常 container.addView(mImageViews.get(position % mImageViews.size()), 0); } catch (Exception e) { // handler something } return mImageViews.get(position % mImageViews.size()); }}
6,TakeTurnsView 最重要的处理轮番的类
/** * Created by 浩 on 2016/8/30. * 轮番图 */public class TakeTurnsView extends LinearLayout { private View root;//根布局 public NoScrollViewPager take_turns_view_pager;//改造后的viewpager private RadioGroup take_turns_radio_group;//存放轮番图下标的radiogroup /** * viewpager的适配器 */ private MyAdapter pagerAdapter; private Handler mHandler;//消息队列,用于自动轮播 private int sleepTime = 3000;//默认轮番时间为3s一轮播 public int getSleepTime() {//设置轮播时间 return sleepTime; } public void setSleepTime(int sleepTime) { this.sleepTime = sleepTime; } /** * 资源集合 */ private List<Drawable> imageDataUrls; /** * 控制viewpager的滑动速度 */ private FixedSpeedScroller scroller; private int fixedTime = 200;//设置viewpager的滑动速度为200ms private UpdateUI updateUI;//viewpager中ui的更新回调接口 public UpdateUI getUpdateUI() { return updateUI; } public void setUpdateUI(UpdateUI updateUI) { this.updateUI = updateUI; if (getUpdateUI() != null && !imageViews.isEmpty()) getUpdateUI().onUpdateUI(0); } public List<Drawable> getImageUrls() { return imageDataUrls; } /** * 传入数据,真正需要显示的数据都在这个方法中处理 * * @param imageUrls */ public void setImageUrls(List<Drawable> imageUrls) { setImageUrls(imageUrls, 0); } public void setImageUrls(List<Drawable> imageUrls, int drawableId) { //为null则不赋值 if (imageUrls == null || imageUrls.isEmpty()) return; //若新传递进来的数据和原来的数据一样,则不处理 //if (imageDataUrls != null && imageDataUrls.equals(imageUrls)) return; //更新数据 imageDataUrls = imageUrls; //若数据长度一样,则不变,若长度不一样,重新赋值 if (tips == null || tips.length != imageUrls.size()) { // 将点点加入到ViewGroup中 tips = new RadioButton[imageUrls.size()]; getRadioButton(drawableId); } //检测数据需不需要更新,只关心数据长度,若长度一样则只更新内容 if (!checkData()) //若数据不一样,则重新赋值 getImageViews(); //适配器在有了数据以后才创建 if (pagerAdapter == null) { pagerAdapter = new MyAdapter(); take_turns_view_pager.setInfinateAdapter(mHandler, pagerAdapter); take_turns_view_pager.setCurrentItem(imageViews.size() * 100); } //更新适配器数据 pagerAdapter.setmImageViews(imageViews); //更新,这里不需要更新适配器 //pagerAdapter.notifyDataSetChanged(); //设置当前点点的位置 tips[take_turns_view_pager.getCurrentItem() < imageDataUrls.size() ? take_turns_view_pager.getCurrentItem() : take_turns_view_pager.getCurrentItem() % imageDataUrls.size()].setChecked(true); //轮番开始 if (mHandler != null) { Contant.isRun = true; mHandler.removeCallbacksAndMessages(null); mHandler.sendEmptyMessage(1); } } /** * 显示点点的布局 * * @param drawableId */ private void getRadioButton(int drawableId) { //添加之前清除点点 take_turns_radio_group.removeAllViews(); //radiobutton的布局 RadioGroup.LayoutParams layoutParams = new RadioGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); //设置边距 int margs = DisplayUtil.dip2px(getContext(), 3); layoutParams.setMargins(margs, margs, margs, margs); //radiobutton的样式 drawableId = drawableId == 0 ? R.drawable.bg_page_item_tag : drawableId; //将点点添加到radiogroup中 for (int i = 0; i < tips.length; i++) { RadioButton radioButton = new RadioButton(getContext()); radioButton.setButtonDrawable(drawableId); radioButton.setLayoutParams(layoutParams); tips[i] = radioButton; take_turns_radio_group.addView(radioButton); } } /** * 将数据添加进inmageview中 */ private void getImageViews() { //清理数据 imageViews.clear(); //图片布局 ViewGroup.LayoutParams imageLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); take_turns_view_pager.setNoScroll(false); // 将图片装载到数组中,这里集中处理,数据为1和数据为2/3的情况 if (imageDataUrls.size() == 1) { for (int i = 0; i < 2; i++) { ImageView imageView = new ImageView(getContext()); imageView.setId(0); imageView.setOnClickListener(new OnItemClickListener(0)); imageView.setScaleType(ImageView.ScaleType.FIT_XY); imageView.setLayoutParams(imageLayoutParams); imageView.setImageDrawable(imageDataUrls.get(0)); imageViews.add(imageView); } take_turns_view_pager.setNoScroll(true); } else if (imageDataUrls.size() == 2 || imageDataUrls.size() == 3) { for (int i = 0; i < imageDataUrls.size() * 2; i++) { ImageView imageView = new ImageView(getContext()); int j; if (i > (imageDataUrls.size() - 1)) { j = i - imageDataUrls.size(); } else { j = i; } imageView.setId(j); imageView.setOnClickListener(new OnItemClickListener(j)); imageView.setScaleType(ImageView.ScaleType.FIT_XY); imageView.setLayoutParams(imageLayoutParams); imageView.setImageDrawable(imageDataUrls.get(j)); imageViews.add(imageView); } } else { for (int i = 0; i < imageDataUrls.size(); i++) { ImageView imageView = new ImageView(getContext()); imageView.setId(i); imageView.setOnClickListener(new OnItemClickListener(i)); imageView.setScaleType(ImageView.ScaleType.FIT_XY); imageView.setLayoutParams(imageLayoutParams); imageView.setImageDrawable(imageDataUrls.get(i)); imageViews.add(imageView); } } } //每一个imageviewd点击事件 private class OnItemClickListener implements OnClickListener { private int viewId; OnItemClickListener(int viewId) { this.viewId = viewId; } @Override public void onClick(View v) { if (getUpdateUI() != null) { getUpdateUI().onItemClick(viewId, (ImageView) v); } } } //判断是否需要重新设置数据 private boolean checkData() { if (imageViews == null || imageViews.isEmpty()) return false; // 将图片装载到数组中 if (imageDataUrls.size() == 1) { if (imageViews.size() == 2) { for (ImageView imageView : imageViews) { imageView.setImageDrawable(imageDataUrls.get(0)); } return true; } return false; } else if (imageDataUrls.size() == 2 || imageDataUrls.size() == 3) { if (imageViews.size() == imageDataUrls.size() * 2) { for (int i = 0; i < imageViews.size(); i++) { imageViews.get(i).setImageDrawable(imageDataUrls.get((i > (imageDataUrls.size() - 1)) ? i - imageDataUrls.size() : i)); } return true; } return false; } else { if (imageViews.size() == imageDataUrls.size()) { for (int i = 0; i < imageViews.size(); i++) { imageViews.get(i).setImageDrawable(imageDataUrls.get(i)); } return true; } return false; } } /** * view控件集合 */ private List<ImageView> imageViews; /** * 游标集合 */ private RadioButton[] tips; public TakeTurnsView(Context context) { super(context); initView(); } public TakeTurnsView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public TakeTurnsView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } private void initView() { root = LayoutInflater.from(getContext()).inflate(R.layout.base_take_truns, this); take_turns_view_pager = (NoScrollViewPager) root.findViewById(R.id.take_turns_view_pager); take_turns_radio_group = (RadioGroup) root.findViewById(R.id.take_turns_radio_group); imageViews = new ArrayList<>(); take_turns_view_pager.addOnPageChangeListener(new MyOnPageChangeListener()); //消息队列接收消息后来通过代码使viewpager进行轮番 //具体逻辑看代码 mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: if (imageDataUrls == null || imageDataUrls.size() <= 1) return; take_turns_view_pager.setCurrentItem(take_turns_view_pager.getCurrentItem() + 1, true); if (Contant.isRun && !Contant.isDown) { this.sendEmptyMessageDelayed(0, sleepTime); } break; case 1: if (Contant.isRun && !Contant.isDown) { this.sendEmptyMessageDelayed(0, sleepTime); } break; } } }; getViewpagerScrollTime(); } /** * 设置viewpager的滑动速度 * 通过反射来重新设置viewpager的滑动速度 */ private void getViewpagerScrollTime() { try { Field field = ViewPager.class.getDeclaredField("mScroller"); field.setAccessible(true); scroller = new FixedSpeedScroller(take_turns_view_pager.getContext(), new AccelerateInterpolator()); field.set(take_turns_view_pager, scroller); //经测试,200ms是最佳视觉效果 scroller.setmDuration(fixedTime); } catch (Exception e) { //LogUtils.e(TAG, "", e); } } /** * 外界调用,赋值viewpager的滑动速度 * * @param time */ public void setViewpagerScrollTime(int time) { try { fixedTime = time; //经测试,200ms是最佳视觉效果 scroller.setmDuration(fixedTime); } catch (Exception e) { //LogUtils.e(TAG, "", e); } } /** * viewpager的滑动监听事件 */ private class MyOnPageChangeListener implements ViewPager.OnPageChangeListener { /** * Indicates that the pager is in an idle, settled state. The current * page is fully in view and no animation is in progress. */ public static final int SCROLL_STATE_IDLE = 0; /** * Indicates that the pager is currently being dragged by the user. */ public static final int SCROLL_STATE_DRAGGING = 1; /** * Indicates that the pager is in the process of settling to a final * position. */ public static final int SCROLL_STATE_SETTLING = 2; @Override public void onPageScrollStateChanged(int state) { switch (state) { case SCROLL_STATE_IDLE: // System.out // .println("===========>>>" // + " onPageScrollStateChanged --->>> SCROLL_STATE_IDLE"); break; case SCROLL_STATE_DRAGGING: // System.out // .println("===========>>>" // + " onPageScrollStateChanged --->>> SCROLL_STATE_DRAGGING"); break; case SCROLL_STATE_SETTLING: // System.out // .println("===========>>>" // + " onPageScrollStateChanged --->>> SCROLL_STATE_SETTLING"); break; } } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { //当viewpager显示出来的时候,将position传递给接口供外部调用 if (imageViews == null || imageViews.size() < 1) return; int j = setImageBackground(position % imageViews.size()); if (getUpdateUI() != null) getUpdateUI().onUpdateUI(j); } /** * 设置选中的tip的背景 * * @param selectItems */ private int setImageBackground(int selectItems) { int j = 0; if (imageDataUrls.size() == 1) {// 说明只有一个图片.默认全部选中 for (int i = 0; i < tips.length; i++) { tips[i].setChecked(true); j = i; } } else if (imageDataUrls.size() == 2 || imageDataUrls.size() == 3) { if (selectItems < imageViews.size() / 2) { for (int i = 0; i < tips.length; i++) { if (i == selectItems) { tips[i].setChecked(true); j = i; } } } else { for (int i = 0; i < tips.length; i++) { if (i == selectItems % imageDataUrls.size()) { tips[i].setChecked(true); j = i; } } } } else { for (int i = 0; i < tips.length; i++) { if (i == selectItems) { tips[i].setChecked(true); j = i; } } } return j; } } /** * 设置viewpager的高度 * * @param height */ public void setTakeTurnsHeight(int height) { root.setMinimumHeight(height); } /** * 轮番图当前显示的图片接口 */ public interface UpdateUI { //当前ui显示出来的位置 void onUpdateUI(int position); //点击了当前页面 void onItemClick(int position, ImageView imageView); } /**设置当前viewpager的ontouch事件,用于解决和外部ontouch的冲突 * @param touchListener */ public void setTouchListener(OnTouchListener touchListener) { if (take_turns_view_pager == null) return; take_turns_view_pager.setOnTouchListener(touchListener); } //跟随activity的生命周期 public void onPause() { Contant.isRun = false; mHandler.removeCallbacksAndMessages(null); } //跟随activity的生命周期 public void onResume() { Contant.isRun = true; mHandler.sendEmptyMessageDelayed(0, sleepTime); }}
6,测试的activity
public class MainActivity extends Activity { private TakeTurnsView takeTurnsView; private Button add_btn, remove_btn, update_btn; private int number; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); takeTurnsView = (TakeTurnsView) findViewById(R.id.default_take_turns_view); add_btn = (Button) findViewById(R.id.add_btn); add_btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { number++; //addDrawables(number); drawables.add(getResources().getDrawable(R.drawable.i2)); takeTurnsView.setImageUrls(drawables); } }); remove_btn = (Button) findViewById(R.id.remove_btn); remove_btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { removeDrawables(); takeTurnsView.setImageUrls(drawables); } }); update_btn = (Button) findViewById(R.id.update_btn); update_btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { updateDrawables(); takeTurnsView.setImageUrls(drawables); } }); takeTurnsView.setSleepTime(4000); takeTurnsView.setViewpagerScrollTime(200); takeTurnsView.setTakeTurnsHeight(300); drawables.add(getResources().getDrawable(R.drawable.i1)); //drawables.add(getResources().getDrawable(R.drawable.i2)); takeTurnsView.setImageUrls(drawables); takeTurnsView.setUpdateUI(new TakeTurnsView.UpdateUI() { @Override public void onUpdateUI(int position) { Log.i("test", "当前图片位置:" + position); } @Override public void onItemClick(int position, ImageView imageView) { Toast.makeText(MainActivity.this, "当前图片位置:" + position + " id:" + imageView.getId(), Toast.LENGTH_LONG).show(); } }); } List<Drawable> drawables = new ArrayList<>(); private void removeDrawables() { drawables.remove(0); } private void updateDrawables() { drawables.set(0, getResources().getDrawable(R.drawable.i4)); } @Override protected void onPause() { super.onPause(); takeTurnsView.onPause(); } @Override protected void onResume() { super.onResume(); //takeTurnsView.onResume(); }}
代码的逻辑主要体现在TakeTurnsView中。
这个轮番图中解决了很多viewpager的问题,代码的逻辑也很清晰,主要分为四大块:
1,适配器 adapter
2,viewpager 轮番页容器
3,scroller 滑动监听
4,TakeTurnsView 轮番页处理中心
这个项目结构很清晰,代码也相对简单,但是逻辑还是比较多的,也称不上复杂就是很多细节需要处理。
也正是因为细节处理的多,所以才衍生出很多bug。
这里附上下载地址供大家学习参考,这其中一定还有我没有考虑到的优化
Demo下载
- android 轮番图——viewpager实现(自动轮番,手动轮番);bug汇总以及解决
- 手动轮番图
- viewpager实现图片轮番(本地图片)
- Android 轮番图
- 轮番图
- Android把定时轮番放到ViewPager里面
- JQuery实现滑动banner轮番图
- 3.vue2.0 轮番图组件实现
- jquery实现图片轮番效果(一)
- jquery实现图片轮番效果(二)
- android 流行的广告轮番图
- 图片轮番
- ajax实现图片轮番滚动
- Android 轮番播放广告图片
- Android 图片轮番 继承ViewGroup实现 可以直接使用
- (四)微信小程序之实现轮番效果(swiper)
- js实现图片轮番效果,原生代码
- 用JavaScript实现图片轮番切换
- 泛型类,方法,接口
- TCP三次握手连接
- Python学习笔记——面向对象编程
- JAVA拾遗 - ArrayList\LinkedList\HashMap源码解析与阅读
- 入侵检测系统的部署
- android 轮番图——viewpager实现(自动轮番,手动轮番);bug汇总以及解决
- android自定义view控件之一圆角背景TextView
- 找主元素
- c语言 split函数的实现
- 数据结构第1章上机实验题
- 在新浪sae中 配置smarty
- [26] Vijos P1978 神奇的幻方(模拟)
- 入侵检测技术的实现
- C++中的智能指针...