Android 焦点轮播图的打造

作为一个刚工作的菜鸟,连个焦点轮播图都撸不出来,真的是感觉没脸见人,查阅多方资料,磕磕绊绊也算是做出来了,但由于不会写,大部分代码也是拼凑起来的,代码不美观不说,复用起来也是极为的不易,寻思还是从头整理一下,做个自定义View,日后复用,查看也更为顺手。
说是自定义view,其实也只是组合view,将不同功能的view组合在一起。

STEP.ONE 滑动展示图片

通过ViewPager可以容易的实现可滑动的展示图片的view。新建三个文件:

  • custom_banner.xml,自定义View布局
  
  
    
  

布局文件,很简单,随着功能的完善,还会添加view

  • Banner.java,自定义view
  public class Banner extends RelativeLayout {
    private String TAG = "Banner";
    private View mView;
    private ViewPager mImage;

    private List mImageList = new ArrayList<>();
    public Banner(Context context, AttributeSet attrs) {
        super(context, attrs);
        mView = LayoutInflater.from(getContext()).inflate(R.layout.custom_banner, this);
        init();
    }
    private void init() {
        mImage = mView.findViewById(R.id.vp_image);
        mImageList = new ArrayList<>();
        mImageList.add(R.mipmap.img_1);
        mImageList.add(R.mipmap.img_2);
        mImageList.add(R.mipmap.img_3);
        ImageAdapter adapter = new ImageAdapter(mImage,mImageList);
        mImage.setAdapter(adapter);
    }
}

public Banner(Context context, AttributeSet attrs)引入布局,init()中简单的添加了几个数据,通过adapter加载。

  • ImageAdapter.java,adapter
public class ImageAdapter extends PagerAdapter {
    private String TAG = "ImageAdapter";
    private ViewPager mImage;
    private List mImageList = new ArrayList<>();

    private int DEFAULT_BANNER_SIZE;
    private int FAKE_BANNER_SIZE;
    public ImageAdapter( ViewPager mImage, List mImageImageList) {
        this.mImage = mImage;
        this.mImageList = mImageImageList;
        this.DEFAULT_BANNER_SIZE = mImageImageList.size();
        this.FAKE_BANNER_SIZE = DEFAULT_BANNER_SIZE + DEFAULT_BANNER_SIZE + DEFAULT_BANNER_SIZE;
    }

    @Override
    public int getCount() {
        return FAKE_BANNER_SIZE;
    }
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        position %= DEFAULT_BANNER_SIZE;
        ImageView img = new ImageView(container.getContext());
        img.setImageResource(mImageList.get(position));
        container.addView(img);
        return img;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        //super.destroyItem(container, position, object);
        container.removeView((View) object);
    }
}

instantiateItem()加载子view,定义了两个变量DEFAULT_BANNER_SIZE和FAKE_BANNER_SIZE,在循环滑动时会用到。

到这里最简单的ViewPager的用法了,这里还有一点需要注意,ViewPager需要重写onMeasure()方法,测量child的高度作为自身高度,否则会match_parent,在banner.java中重写onMeasure()。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int maxHeight = 0;
        for (int i = 0; i < mImage.getChildCount(); i++) {
            View child = mImage.getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            maxHeight = Math.max(maxHeight,child.getMeasuredHeight());
        }
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

测量child的高度,最大值作为父容器高度。

STEP.TWO 实现循环与自动播放

实现循环重写PagerAdapter中的finishUpdate(),自动播放使用轻量级的定时器Timer。

  • 循环。在ImageAdapter.java中重写finishUpdate()。
public class ImageAdapter extends PagerAdapter {
    private String TAG = "ImageAdapter";
    private ViewPager mImage;
    private List mImageList = new ArrayList<>();

    private int DEFAULT_BANNER_SIZE;
    private int FAKE_BANNER_SIZE;
    public ImageAdapter( ViewPager mImage, List mImageImageList) {
        this.mImage = mImage;
        this.mImageList = mImageImageList;
        this.DEFAULT_BANNER_SIZE = mImageImageList.size();
        this.FAKE_BANNER_SIZE = DEFAULT_BANNER_SIZE + DEFAULT_BANNER_SIZE + DEFAULT_BANNER_SIZE;
    }

    @Override
    public int getCount() {
        return FAKE_BANNER_SIZE;
                      .
                      .
                      .
    @Override
    public void finishUpdate(ViewGroup container) {
        super.finishUpdate(container);
        int curPosition = mImage.getCurrentItem();
        if (0 == curPosition){
            //滑动到最左边
            mImage.setCurrentItem(DEFAULT_BANNER_SIZE, false);
        }
        if ((FAKE_BANNER_SIZE -1) == curPosition){
            //滑动到最右边
            mImage.setCurrentItem(FAKE_BANNER_SIZE - DEFAULT_BANNER_SIZE - 1, false);
        }
    }
}

DEFAULT_BANNER_SIZE是图片列表的大小,FAKE_BANNER_SIZE是三倍图片列表的小。
以代码为例,目前我们的ViewPager有3屏(DEFAULT_BANNER_SIZE),但是如果在PagerAdapter的getCount方法中我们返回9(FAKE_BANNER_SIZE),即告诉ViewPager我们有9屏。那么如果当前位于第3屏,由于对于ViewPager来说它认为有9屏,所以我们仍然可以向后滑动。当滑动到第9屏时(右边界),立即将第6屏的view返回给他,这样ViewPager依然可以向右滑动。同理处于第1屏时(左边界),讲第4屏的view返回给他,此时仍然可以左滑。这样便实现了循环播放。如图..............hahah


Android 焦点轮播图的打造_第1张图片
示例图
  • 自动播放
public class Banner extends RelativeLayout {
    private int DEFAULT_BANNER_SIZE;
    private int FAKE_BANNER_SIZE;
    //{start update 实现自动播放
    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    int position = (mImage.getCurrentItem() + 1) % DEFAULT_BANNER_SIZE + DEFAULT_BANNER_SIZE;
                    mImage.setCurrentItem(position, true);
                    Log.d(TAG, "handleMessage: "+position);
                    break;
                default:
                    break;
            }
        }
    };
    // end }

    private List mImageList = new ArrayList<>();
    public Banner(Context context, AttributeSet attrs) {
        super(context, attrs);
        mView = LayoutInflater.from(getContext()).inflate(R.layout.custom_banner, this);
        init();
        //调用
        startTimer();
    }
    //{start update
    private void startTimer() {
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                Message message = new Message();
                message.what = 1;
                handler.sendMessage(message);
            }
        };
        timer.schedule(task,5000,5000);
    }
    //end }
}

使用Timer定义一个5秒执行的任务,通过Headler异步执行。

STEP.THREE 底部圆点

根据mImageList动态添加底部圆点。

  • 修改custom_banner.xml文件


    
    

新增了LinearLayout布局,添加圆点的任务就交给他了。

  • 修改Banner.java
private void initPoint() {
        for (int i = 0; i < mImageList.size(); i++) {
            View view = new View(getContext());
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(15, 15);
            params.leftMargin = 10;
            params.rightMargin = 10;
            view.setLayoutParams(params);
            if (i == 0) {
                view.setBackgroundResource(R.drawable.shape_point_select);
            } else {
                view.setBackgroundResource(R.drawable.shape_point_default);
            }
            final int finalI = i;
            view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    int item = finalI+DEFAULT_BANNER_SIZE;
                    mImage.setCurrentItem(item, true);
                }
            });
            mPoint.addView(view);
            mPointList.add(view);
        }
    }

在mImageList初始化后执行该函数,添加圆点,背景,和点击事件。

  • 发生点击事件后,修改圆点背景
mImage.addOnPageChangeListener(this);

 @Override
    public void onPageSelected(int position) {
        position %= DEFAULT_BANNER_SIZE;
        for (View view : mPointList) {
            view.setBackgroundResource(R.drawable.shape_point_default);
        }
        mPointList.get(position).setBackgroundResource(R.drawable.shape_point_select);
    }

添加监听,当某页面被选中时,先遍历列表,将所有圆点都设置为未选中状态,再将对应页面的背景设置为选中背景。

END

到这里这个功能就已经全部实现了,这也是我的第一篇技术文章,参考了挺多大大的博客,最后倒腾出来。文章感觉写的干巴巴,一来我本人对有些也不算特别熟悉,二来文章写的少。坚持写博客,到自己的文章干货越来越多,越写越写流畅,自己的成长了多少也就很明确了。当然也希望这篇文章可以帮助到大家。

参考链接

循环广告位组件的实现
动态圆点轮播图
解决ViewPager高度无法wrap_content问题

你可能感兴趣的:(Android 焦点轮播图的打造)