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下载

0 0
原创粉丝点击