Android开发Android开发Android开发经验谈

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

2018-11-05  本文已影响5人  d74f37143a31

推荐两个库

大神们已经写好轮子了,需要的自取,取完记得顺手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 = new Handler(){
    @Override
    public void handleMessage(android.os.Message msg) {
        // 让ViewPager滑到下一页
        bannerViewpager.setCurrentItem(bannerViewpager.getCurrentItem()+1);

    }
};
// 记得在 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();
        }

    }

}

上一篇下一篇

猜你喜欢

热点阅读