美文网首页Android开发Android开发Android开发经验谈
Android 手写一个简单的无限轮播 Banner

Android 手写一个简单的无限轮播 Banner

作者: d74f37143a31 | 来源:发表于2018-11-05 23:26 被阅读5次

    推荐两个库

    大神们已经写好轮子了,需要的自取,取完记得顺手star即可,非常感谢开源的作者们。

    1. youth5201314/banner
    2. bingoogolapple/BGABanner-Android
    3. Bigkoo/Android-ConvenientBanner

    实现效果

    只有图片无限循环,需要添加标题一级指示器下标自行实现。
    (忽略右边竖线背景,那是录屏没对好位置)

    banner.gif

    布局

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="wrap_content"
        android:layout_height="130dp"
        android:layout_gravity="center"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:clipChildren="false"
        android:overScrollMode="never"/>
    

    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp"
    android:clipChildren="false"

    这三行代码主要是实现 ViewPager Item 的两边能够漏出,如果还没有效果可尝试在代码添加

    bannerViewpager.setOffscreenPageLimit(2);
    bannerViewpager.setPageMargin(10);
    bannerViewpager.setClipChildren(false);
    

    滚动

    ViewPager 自身就是能滚动,但是需要我们手动去滑,而手动去滑调用的是ViewPager的'setCurrentItem()'方法:
    只有一个参数的是滑动伴随着动画,有两个参数的可以设置滑动是否需要伴随动画,参数二设置为 false 则没有动画。
    目前 Android 版的 微信 底部 tab 切换就是没有动画的效果.

    /**
         * Set the currently selected page. If the ViewPager has already been through its first
         * layout with its current adapter there will be a smooth animated transition between
         * the current item and the specified item.
         *
         * @param item Item index to select
         */
        public void setCurrentItem(int item) {
            mPopulatePending = false;
            setCurrentItemInternal(item, !mFirstLayout, false);
        }
    
        /**
         * Set the currently selected page.
         *
         * @param item Item index to select
         * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
         */
        public void setCurrentItem(int item, boolean smoothScroll) {
            mPopulatePending = false;
            setCurrentItemInternal(item, smoothScroll, false);
        }
    

    既然知道了ViewPager切换调用的方法,那么我们执行一个定时任务去定时执行切换就可以实现ViewPager自动轮播了。
    我这里使用Handler每隔两秒发送一次消息,在接收到消息后切换页面进行轮播

    定时任务 :在Android开发中,定时执行任务的3种实现方法, 这篇博客介绍了三种方式,还可以通过Executors.newScheduledThreadPool()线程池的方式,线程池的方式优点是方便控制启动和关闭轮播。

    • 定义一个 Handler, 处理消息,实现自动轮播
    
    Handler handler = new Handler(){
        @Override
        public void handleMessage(android.os.Message msg) {
            // 让ViewPager滑到下一页
            bannerViewpager.setCurrentItem(bannerViewpager.getCurrentItem()+1);
    
        }
    };
    
    • 在 OnCreate 中发送延时定时消息
    // 记得在 OnDestory 中设为 false
    private boolean isRunning = true;
    // 判空
    if (mBannerAdapter != null){
        mBannerAdapter.notifyDataSetChanged();
    }
    //延时,循环调用handler
    if(isRunning){
        handler.sendEmptyMessageDelayed(0, 2000);
    }
    

    无限循环

    这是重点,这里参考了网上大神的写法,地址已经找不到了,是在掘金上看到的,我这里就直接搬代码过来,讲讲我的理解了(作者代码注释已经很明白了,我就是找我能说的说几句,哈哈),先上代码。

    public class BannerAdapter extends PagerAdapter {
    
        private Context mContext;
        private ViewPager mViewPager;
        private ItemClickListener itemClickListener;
        private ArrayList<String> images = new ArrayList<>();
    
    
        public BannerAdapter(Context context, ArrayList<String> images, ViewPager viewPager) {
            mContext = context;
            this.mViewPager = viewPager;
            this.images = images;
        }
    
        public void setItemClickListener(ItemClickListener itemClickListener) {
            this.itemClickListener = itemClickListener;
        }
    
        public interface ItemClickListener {
            void onItemClick(int index);
        }
    
        @Override
        public int getCount() {
            // 无限轮播
            return images.size()+2;
        }
    
        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }
    
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
    
            // 取到的真实位置
            position %= images.size();
    
            // 布局
            View view = LayoutInflater.from(mContext).inflate(R.layout.item_banner_viewpager, null);
    
            // 自定义的圆角图片控件
            RoundImageView imageView = view.findViewById(R.id.iv_item_banner);
            imageView.setRoundRadius(DensityUtils.dp2px(mContext,10));
            Glide.with(mContext).load(images.get(position)).placeholder(R.drawable.image_holder).into(imageView);
    
            // 设置点击事件
            final int finalPosition = position;
            imageView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (null != itemClickListener) {
                        itemClickListener.onItemClick(finalPosition);
                    }
                }
            });
            container.addView(view);
            return view;
        }
    
    
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }
    
        /**
         * Called when the a change in the shown pages has been completed.  At this
         * point you must ensure that all of the pages have actually been added or
         * removed from the container as appropriate.
         * @param container The containing View which is displaying this adapter's
         * page views.
         * 
         *  这是PagerAdapter中的方法,它的调用方式是在页面改变完成的时候调用,
         */
        @Override
        public void finishUpdate(ViewGroup container) {
            int position = mViewPager.getCurrentItem();
            /**
             *  以下是原博主原话:也是实现切换的关键之处
             *
             *  第五这里获得当前的 positon 然后对其setCurrentItem进行变换
             *  这里设置当 position=0 时把 position 设置为图片列表的最大值
             *  是为了 position=0 时左滑显示最后一张,我举个例子这里 ImageSize 是 5
             *  当 position==0 时设置为5,左滑就是 position=4,也就是第五张图片,
             *
             *  if (position == (ImageSize+2) - 1)
             *  这个判断 (ImageSize+2) 这个是给 viewpager 设置的页面数,这里是7
             *  当 position==7-1=6 时,这时 viewpager 就滑到头了,所以把 currentItem 设置为1
             *  这里设置为 1 还是为了能够左滑,这时左滑 position=0 又执行了第一个判断又设置为5,
             *  这样就实现了无限轮播的效果
             *  setCurrentItem(position,false);
             *  这里第二个参数false是消除viewpager设置item时的滑动动画,不理解的去掉它运行下就知道啥意思了
             *
             */
            if (position == 0) {
                position = images.size();
                mViewPager.setCurrentItem(position,false);
            } else if (position == (images.size()+2) - 1) {
                position = 1;
                mViewPager.setCurrentItem(position,false);
            }
        }
    
    }
    

    附上 Activity 代码

    import android.os.Bundle;
    import android.os.Handler;
    import android.support.v4.view.ViewPager;
    import android.support.v7.app.AppCompatActivity;
    
    import com.dong.smilingconstellation.R;
    
    import java.util.ArrayList;
    
    public class TestActivity extends AppCompatActivity {
    
    
    
        private Handler handler = new Handler(){
            @Override
            public void handleMessage(android.os.Message msg) {
                // 让ViewPager滑到下一页
                mBannerViewpager.setCurrentItem(mBannerViewpager.getCurrentItem()+1);
    
                //延时,循环调用handler
                if(isRunning){
                    handler.sendEmptyMessageDelayed(0, 2000);
                }
    
                // 判空
                if (mBannerAdapter != null){
                    mBannerAdapter.notifyDataSetChanged();
                }
            }
        };
    
        private boolean isRunning = true;
        private ViewPager mBannerViewpager;
        private BannerAdapter mBannerAdapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_test);
    
            mBannerViewpager = findViewById(R.id.viewpager);
            mBannerAdapter = new BannerAdapter(this, getImages(), mBannerViewpager);
            mBannerViewpager.setAdapter(mBannerAdapter);
            mBannerViewpager.setOffscreenPageLimit(2);
            mBannerViewpager.setPageMargin(10);
            mBannerViewpager.setClipChildren(false);
    
            handler.sendEmptyMessageDelayed(0, 2000);
        }
    
        private ArrayList<String> getImages() {
            ArrayList<String> images = new ArrayList<>();
            images.add("http://dpic.tiankong.com/m5/ag/QJ6490655499.jpg");
            images.add("http://dpic.tiankong.com/ww/03/QJ7103333276.jpg");
            images.add("http://dpic.tiankong.com/76/5b/QJ6461022316.jpg");
            images.add("http://dpic.tiankong.com/pc/2s/QJ7100984563.jpg");
            images.add("http://dpic.tiankong.com/un/r6/QJ6542185957.jpg");
            return images;
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            isRunning = false;
            handler.removeCallbacksAndMessages(null);
        }
    }
    
    

    圆角图片控件

    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapShader;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Matrix;
    import android.graphics.Paint;
    import android.graphics.RectF;
    import android.graphics.Shader;
    import android.graphics.drawable.BitmapDrawable;
    import android.graphics.drawable.Drawable;
    import android.support.v7.widget.AppCompatImageView;
    import android.util.AttributeSet;
    import android.util.TypedValue;
    
    import com.scwang.smartrefresh.layout.util.DensityUtil;
    
    /**
     * Created by Administrator on 2018/11/5.
     */
    
    public class RoundImageView extends AppCompatImageView {
        private Paint mPaint;
    
        private int mWidth;
    
        private int mHeight;
    
        private int mRadius;//圆半径
    
        private RectF mRect;//矩形凹行大小
    
        private int mRoundRadius;//圆角大小
    
        private BitmapShader mBitmapShader;//图形渲染
    
        private Matrix mMatrix;
    
        private int mType = 1;// 记录是圆形还是圆角矩形,默认是圆角矩形
    
        public static final int TYPE_CIRCLE = 0;//圆形
        public static final int TYPE_ROUND = 1;//圆角矩形
        public static final int TYPE_OVAL = 2;//椭圆形
        public static final int DEFAULT_ROUND_RADIUS = DensityUtil.dp2px(10);//默认圆角大小
    
        public RoundImageView(Context context) {
            this(context, null);
            // TODOAuto-generated constructor stub
        }
    
        public RoundImageView(Context context, AttributeSet attrs) {
            this(context,attrs, 0);
            // TODOAuto-generated constructor stub
        }
    
        public RoundImageView(Context context, AttributeSet attrs,int defStyle){
            super(context,attrs, defStyle);
            initView();
        }
    
        private void initView() {
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mMatrix = new Matrix();
            mRoundRadius = DEFAULT_ROUND_RADIUS;
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
            // TODOAuto-generated method stub
            super.onMeasure(widthMeasureSpec,heightMeasureSpec);
            // 如果是绘制圆形,则强制宽高大小一致
            if (mType ==TYPE_CIRCLE) {
                mWidth = Math.min(getMeasuredWidth(),getMeasuredHeight());
                mRadius = mWidth / 2;
                setMeasuredDimension(mWidth, mWidth);
            }
    
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
    
            if (null ==getDrawable()) {
                return;
            }
            setBitmapShader();
            if (mType ==TYPE_CIRCLE) {
                canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
            } else if (mType == TYPE_ROUND) {
                mPaint.setColor(Color.RED);
                canvas.drawRoundRect(mRect, mRoundRadius, mRoundRadius, mPaint);
            }else if(mType == TYPE_OVAL){
                canvas.drawOval(mRect, mPaint);
            }
        }
    
        @Override
        protected void onSizeChanged(int w,int h, int oldw,int oldh) {
            // TODOAuto-generated method stub
            super.onSizeChanged(w,h, oldw, oldh);
            mRect = new RectF(0,0, getWidth(), getHeight());
        }
    
        /**
         * 设置BitmapShader
         */
        private void setBitmapShader() {
            Drawable drawable = getDrawable();
            if (null ==drawable) {
                return;
            }
            Bitmap bitmap = drawableToBitmap(drawable);
            // 将bitmap作为着色器来创建一个BitmapShader
            mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
            float scale =1.0f;
            if (mType ==TYPE_CIRCLE) {
                // 圆形
                // 拿到bitmap宽或高的小值
                int bSize =Math.min(bitmap.getWidth(), bitmap.getHeight());
                scale = mWidth * 1.0f /bSize;
    
            } else if (mType == TYPE_ROUND ||mType == TYPE_OVAL) {
                // 圆角,椭圆
                // 如果图片的宽或者高与view的宽高不匹配,计算出需要缩放的比例;缩放后的图片的宽高,一定要大于我们view的宽高;所以我们这里取大值;
                scale = Math.max(getWidth() * 1.0f/ bitmap.getWidth(), getHeight() * 1.0f / bitmap.getHeight());
            }
            // shader的变换矩阵,我们这里主要用于放大或者缩小
            mMatrix.setScale(scale,scale);
            // 设置变换矩阵
            mBitmapShader.setLocalMatrix(mMatrix);
            mPaint.setShader(mBitmapShader);
    
        }
    
        /**
         * drawable转bitmap
         *
         * @paramdrawable
         * @return
         */
        private Bitmap drawableToBitmap(Drawable drawable) {
            if (drawable instanceof BitmapDrawable) {
                BitmapDrawable bitmapDrawable =(BitmapDrawable) drawable;
                return bitmapDrawable.getBitmap();
            }
            int w =drawable.getIntrinsicWidth();
            int h =drawable.getIntrinsicHeight();
            Bitmap bitmap = Bitmap.createBitmap(w,h, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, w, h);
            drawable.draw(canvas);
            return bitmap;
        }
        /**
         * 单位dp转单位px
         */
        public int dpTodx(int dp){
    
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    dp,getResources().getDisplayMetrics());
        }
    
        public int getType(){
            return mType;
        }
        /**
         * 设置图片类型:圆形、圆角矩形、椭圆形
         * @param mType
         */
        public void setType(int mType) {
            if(this.mType !=mType){
                this.mType = mType;
                invalidate();
            }
    
        }
        public int getRoundRadius() {
            return mRoundRadius;
        }
        /**
         * 设置圆角大小
         * @parammRoundRadius
         */
        public void setRoundRadius(int mRoundRadius) {
            if(this.mRoundRadius !=mRoundRadius){
                this.mRoundRadius =mRoundRadius;
                invalidate();
            }
    
        }
    
    }
    
    

    相关文章

      网友评论

        本文标题:Android 手写一个简单的无限轮播 Banner

        本文链接:https://www.haomeiwen.com/subject/vxbexqtx.html