Android 手写一个简单的无限轮播 Banner
2018-11-05 本文已影响5人
d74f37143a31
推荐两个库
大神们已经写好轮子了,需要的自取,取完记得顺手
star
即可,非常感谢开源的作者们。
实现效果
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();
}
}
}