前段时间自己自定义了一个轮播图,使用起来很简单,分享给大家使用,如有什么缺陷或是问题,请留言提出建议。
我自定义的类叫做BannerView.java,整个轮播图的实现都在这个类里面,在这个类里面我向外提供了很多方法,可以根据项目情况或是个人喜欢定义,比如有一个设置指示器位置的方法(左中右,默认是居中),一个设置指示器样式的方法,默认是引用我自己画的圆点,想要其他样式可以自己去定义,还有一个就是播放的时间方法,BannerView里面注释都很清楚。
先看看效果图吧,因为图片颜色原因在这里指示器看不是很清楚
这里分别使用 java代码 跟 kotlin代码 两种形式来实现功能
在看看使用的方法
一、在布局文件中添加自定义的轮播图控件
二、添加图片资源,这里添加了三张图片
三、最后一步,让轮播图轮播起来,那就是开启定时器,关闭页面是关闭定时器,在生命周期中调用
自此,轮播图功能就实现了,是不是很简单,好了接下来就是BannerView.java这个最重要的类了,简单说一下某些主要的实现功能,下文后面会给出完整代码
既然是自定义View,不关联任何布局xml文件,那么首先当然是动态创建最外层布局,我用了一个FrameLayout,里面包含一个ViewPager,在最下面是一个LinearLayout,用来存放指示器的,实现代码如下:
接下来是指示器的实现,也是动态创建的,同时还要设置指示器的位置,动态创建的过程代码量比较多,就不贴出。
这个自定义轮播图的原理是使用ViewPager来实现的,所以肯定需要一个ViewPager的适配器,里面最主要的方法是根据外面setData()时传入的List 的size来动态创建ImageView,然后使用Glide来加载图片,代码如下:
还有一点就是轮播图是可以手动滑动的,所以有一个需要注意的问题就是,当你手动滑动的时候,轮播图的自动播放功能要失效,当你不手动去滑动的时候,轮播图又可以自动去轮播,所以需要一个boolean去判断当前是否是出于手动滑动状态,若是,定时器就不向handler发送信息,不做切换图片的操作,当没有手动滑动,定时器就像handler发送信息,实现图片切换,代码如下:
好了就讲这么多了,其实挺简单的,若觉得不好,请给出建议,大家一起学习,若觉得好,就关注分享给身边需要的朋友吧
BannerView.java代码:
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.bumptech.glide.Glide;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import qin.zszc.com.basiclib.R;
/**
* Created by xcfor on 2018/7/11.
*/
public class BannerView extends FrameLayout implements ViewPager.OnPageChangeListener {
public final int INDICATOR_LOCATION_CENTER_BUTTON = 0x1;//指示器居中
public final int INDICATOR_LOCATION_LEFT_BUTTON = 0x2;//指示器居左
public final int INDICATOR_LOCATION_RIGHT_BUTTON = 0x3;//指示器居右
public final int BANNER_DELAY_TIME = 1000; //轮播图延迟时间
public final int BANNER_DEF_TIIME = 5000;//轮播图默认切换时间
/**
* 存放圆点指示器
*/
private LinearLayout mIndecatorLayout;
private PagerAdapter mAdapter;
private List<String> mUrls; //图片路径list
private Context mContext;
private ViewPager mViewPager;
private boolean mIsScrollingByUser;// 用户手动滑动广告中
private int mIndicatorDotWidth;//轮播切换小圆点宽度默认宽度
private int mCurrentPos = 0;//当前页pos
private int mPrePos = 0; //历史页pos
private int mCount;//轮播图总数
private int mIndicatorLocation = 1;//默认值为1:居中 2:左 3:右
private boolean mChangeIndecatorStyle = false;//是否自定义指示器的样式
private float mScale;//显示度量的密度
/**
* 定时滚动定时器
*/
private Timer mTimer;
private TimerTask mTask;
/**
* 接收定时器信息的handler
*/
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
int what = msg.what;
switch (what) {
case 0:
if (mViewPager != null) {
if (!mIsScrollingByUser) {
if (mCurrentPos == mUrls.size()) {
mCurrentPos = 0;
} else {
mCurrentPos++;
}
}
mViewPager.setCurrentItem(mCurrentPos);
}
break;
}
}
};
public BannerView(@NonNull Context context) {
super(context);
this.mContext = context;
}
public BannerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
}
public BannerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void init() {
if (mUrls.size() != 1) {
mViewPager.addOnPageChangeListener(this);
}
//切换画面
mViewPager.setPageTransformer(true, new MyPageTransformer());
mIndecatorLayout.removeAllViews();
//向线性布局中添加小圆点指示器
if (mUrls.size()>1){
View dot;
LinearLayout.LayoutParams params;
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
int width = displayMetrics.widthPixels;
for (int i = 0; i < mCount; i++) {
dot = new View(mContext);
params = new LinearLayout.LayoutParams(mIndicatorDotWidth, mIndicatorDotWidth);
params.setMargins(mIndicatorDotWidth, 0, 0, dip2px(10));
dot.setLayoutParams(params);
dot.setBackgroundResource(R.drawable.basiclib_dot_bg_selector);
dot.setEnabled(false);//默认设为非选中
mIndecatorLayout.addView(dot);
}
//指示器图标的位置
switch (mIndicatorLocation){
case INDICATOR_LOCATION_CENTER_BUTTON:
if (mCount % 2 == 0) {
mIndecatorLayout.setPadding(width / 2 - (mCount * mIndicatorDotWidth), 0, 0, 8);
} else {
mIndecatorLayout.setPadding(width / 2 - (mCount * mIndicatorDotWidth), 0, 0, 8);
}
break;
case INDICATOR_LOCATION_LEFT_BUTTON:
mIndecatorLayout.setPadding(0, 0, 0, 8);
break;
case INDICATOR_LOCATION_RIGHT_BUTTON:
mIndecatorLayout.setPadding(width - ((mCount * 2+1) * mIndicatorDotWidth), 0, 0, 8);
break;
}
int midPos = Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % mCount;
mCurrentPos = midPos;
mIndecatorLayout.getChildAt(0).setEnabled(true);
mViewPager.setCurrentItem(midPos);
}else {
mIndecatorLayout.setVisibility(GONE);
}
}
/**
* 设置指示器的位置
*
* @param location
*/
public void relayoutIndicator(int location) {
mIndicatorLocation = location;
}
/**
* 自定义指示器入口
*
* @param style
*/
public void customerIndicatorEntry(int style) {
if (!mChangeIndecatorStyle) {
return;
} else {
changeIndicator(style);
}
}
/**
* 改变指示器
* @param style
*/
public void changeIndicator(int style) {
for (int i = 0; i < mCount; i++) {
mIndecatorLayout.getChildAt(i).setBackgroundResource(style);
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
//动态创建轮播图最外围布局
mIndicatorDotWidth = dip2px(4);
FrameLayout fl = new FrameLayout(mContext);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mViewPager = new ViewPager(mContext);
mViewPager.setLayoutParams(params);
fl.addView(mViewPager);
//动态创建指示器外围布局
mIndecatorLayout = new LinearLayout(mContext);
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.BOTTOM;
mIndecatorLayout.setLayoutParams(params);
mIndecatorLayout.setOrientation(LinearLayout.HORIZONTAL);
fl.addView(mIndecatorLayout);
addView(fl);
}
/**
* 获取数据
*/
public void setData(List<String> urls) {
if (urls.isEmpty()) {
return;
}
this.mUrls = urls;
mCount = mUrls.size();
if (mAdapter == null) {
mAdapter = new MyAdapter(mUrls);
mViewPager.setAdapter(mAdapter);
}else{
//更新viewpager视图
//--更新原点指示器
mAdapter.notifyDataSetChanged();
}
init();
}
/**
* 默认时间启动定时器
*/
public void startBannerScrollTask(){
if (mUrls.size() != 1){
startBannerScrollTask(BANNER_DEF_TIIME);
}
}
/**
* 给外边调用,可以使用外边的适配器
* @param timer
*/
public void setScrollTask(Timer timer ,long timeSpace){
if (mCount == 0){
return;
}
mTask = new TimerTask() {
@Override
public void run() {
if (!mIsScrollingByUser){
mHandler.sendEmptyMessage(0);
}
}
};
timer.schedule(mTask,BANNER_DELAY_TIME, timeSpace);
}
//开启轮播图定时器
public void startBannerScrollTask(long timeSpace) {
if (mCount == 0) {
return;
}
mTimer = new Timer(true);
mTask = new TimerTask() {
@Override
public void run() {
if (!mIsScrollingByUser){
mHandler.sendEmptyMessage(0);
}
}
};
mTimer.schedule(mTask, BANNER_DELAY_TIME, timeSpace);//1000ms后按指定时间间隔轮播
}
/**
* 关闭轮播图定时器
*/
public void stopBannerTask() {
if (mTask != null) {
mTask.cancel();
}
}
/**
* 根据手机的分辨率从 dip 的单位 转成为 px(像素)
* 改全变量
*/
public int dip2px(float dpValue) {
mScale = mContext.getResources().getDisplayMetrics().density;
return (int) (dpValue * mScale + 0.5f);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mCurrentPos = position;
mIndecatorLayout.getChildAt(mPrePos % mUrls.size()).setEnabled(false);
mIndecatorLayout.getChildAt(mCurrentPos % mUrls.size()).setEnabled(true);//设置true放后面,防止初始化时两个pos都为0时。没有默认选中
mPrePos = mCurrentPos;
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == 0) {//用户手动滑动广告时,取消自动翻页响应
mIsScrollingByUser = false;
} else {
//用户手动滑动中
mIsScrollingByUser = true;
}
}
/**
* 轮播图ViewPager适配器
*/
class MyAdapter extends PagerAdapter {
List<String> addUrls;
public MyAdapter(List<String> addUrls) {
this.addUrls = addUrls;
}
@Override
public int getCount() {
if (addUrls.size() == 1){
return addUrls.size();
}else {
return Integer.MAX_VALUE;
}
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView imageView = new ImageView(mContext);
String currentUrl = addUrls.get(position % addUrls.size());
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
Glide.with(mContext).load(currentUrl).into(imageView);
container.addView(imageView);
return imageView;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
}
/**
* 添加viewPager页面切换效果
*/
class MyPageTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(View page, float position) {
final float normalizedposition = Math.abs(Math.abs(position) - 1);
page.setAlpha(normalizedposition);
}
}
}
BannerView.kt代码:
import android.annotation.SuppressLint
import android.content.Context
import android.os.Handler
import android.os.Message
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import com.bumptech.glide.Glide
import com.example.testproject.R
import java.util.*
class BannerView : FrameLayout, ViewPager.OnPageChangeListener {
private val INDICATOR_LOCATION_CENTER_BUTTON: Int = 0x1//指示器居中
private val INDICATOR_LOCATION_LEFT_BUTTON: Int = 0x2//指示器居左
private val INDICATOR_LOCATION_RIGHT_BUTTON: Int = 0x3//指示器居右
private val BANNER_DELAY_TIME: Long = 1000//轮播图延迟时间
private val BANNER_DEF_TIIME: Long = 5000//轮播图默认切换时间
private var mIndecatorLayout: LinearLayout? = null
private var mAdapter: PagerAdapter? = null
private var mUrls: MutableList<String>? = null
private var mViewPager: ViewPager? = null
private var mContext: Context? = null
private var mIsScrellingByUser: Boolean = false
private var mIndicatorDotWidth: Int = 0
private var mCurrentPos: Int = 0
private var mPrePos: Int = 0
private var mCount: Int = 0
private var mIndicatorLocation: Int = 1
private var mChangeIndecatorStyle: Boolean = false
private var mScale: Float = 0.0f
private var mTimer: Timer? = null
private var mTask: TimerTask? = null
constructor(context: Context) : super(context) {
this.mContext = context
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
this.mContext = context
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){
this.mContext = context
}
private var mHandler: Handler = @SuppressLint("HandlerLeak")
object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
val what = msg.what
if (what == 0) {
if (!mIsScrellingByUser) {
mCurrentPos = if (mCurrentPos == mUrls?.size) 0
else mCurrentPos.plus(1)
}
mViewPager?.currentItem = mCurrentPos
}
}
}
private fun init() {
if (mUrls?.size != 1)
mViewPager?.addOnPageChangeListener(this)
//切换画面
mViewPager?.setPageTransformer(true, MyPageTransformer())
mIndecatorLayout?.removeAllViews()
//向线性布局中添加小圆点指示器
if (mUrls?.size?.compareTo(1)!! > 0) {
var dot: View
var params: LinearLayout.LayoutParams
val windowManager: WindowManager = mContext?.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(display)
val width: Int = display.widthPixels
for (item in 0 until mCount) {
dot = View(mContext)
params = LinearLayout.LayoutParams(mIndicatorDotWidth, mIndicatorDotWidth)
params.setMargins(mIndicatorDotWidth, 0, 0, dip2px(10f))
dot.layoutParams = params
dot.setBackgroundResource(R.mipmap.ic_launcher)
dot.isEnabled = false
mIndecatorLayout?.addView(dot)
}
//指示器图标的位置
when (mIndicatorLocation) {
INDICATOR_LOCATION_CENTER_BUTTON ->
if (mCount % 2 == 0)
mIndecatorLayout?.setPadding(width / 2 - (mCount * mIndicatorDotWidth), 0, 0, 8)
else
mIndecatorLayout?.setPadding(width / 2 - (mCount * mIndicatorDotWidth), 0, 0, 8)
INDICATOR_LOCATION_LEFT_BUTTON -> mIndecatorLayout?.setPadding(0, 0, 0, 8)
INDICATOR_LOCATION_RIGHT_BUTTON -> mIndecatorLayout?.setPadding(width - ((mCount * 2 + 1) * mIndicatorDotWidth), 0, 0, 8)
}
val midPos: Int = Int.MAX_VALUE / 2 - Int.MAX_VALUE / 2 % mCount
mCurrentPos = midPos
mIndecatorLayout?.getChildAt(0)?.isEnabled = true
mViewPager?.currentItem = midPos
} else {
mIndecatorLayout?.visibility = View.GONE
}
}
/**
* 设置指示器的位置
*/
fun relayoutIndicator(location: Int) {
mIndicatorLocation = location
}
/**
* 自定义指示器入口
*/
fun customerIndicatorEntry(style: Int) {
if (!mChangeIndecatorStyle)
return
else
changeIndicator(style)
}
/**
* 改变指示器
*/
private fun changeIndicator(style: Int) {
for (item in 0 until mCount) {
mIndecatorLayout?.getChildAt(item)?.setBackgroundResource(style)
}
}
private fun dip2px(dpValue: Float): Int {
mScale = mContext?.resources?.displayMetrics?.density!!
return (dpValue * mScale + 0.5f).toInt()
}
override fun onFinishInflate() {
super.onFinishInflate()
//动态创建轮播图最外围布局
mIndicatorDotWidth = dip2px(4f)
val fl = FrameLayout(mContext!!)
val params = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
mViewPager = ViewPager(mContext!!)
mViewPager?.layoutParams = params
fl.addView(mViewPager)
//动态创建指示器外围布局
mIndecatorLayout = LinearLayout(mContext!!)
params.height = ViewGroup.LayoutParams.WRAP_CONTENT
params.gravity = Gravity.BOTTOM
mIndecatorLayout?.layoutParams = params
mIndecatorLayout?.orientation = LinearLayout.HORIZONTAL
fl.addView(mIndecatorLayout)
addView(fl)
}
/**
* 获取数据
*/
fun setData(urls: MutableList<String>) {
if (urls.isEmpty()) return
this.mUrls = urls
mCount = mUrls!!.size
if (mAdapter == null) {
mAdapter = MyAdapter(mUrls!!, mContext!!)
mViewPager?.adapter = mAdapter
} else {
mAdapter?.notifyDataSetChanged()
}
init()
}
/**
* 启动定时器(默认时间)
*/
fun startBannerScrollTask() {
if (mUrls?.size != 1)
startBannerScrollTask(BANNER_DEF_TIIME)
}
/**
* 给外部调用,可以使用外边的适配器
*/
fun setScrollTask(timer: Timer, timeSpace: Long) {
if (mCount == 0)
return
mTask = object : TimerTask() {
override fun run() {
if (!mIsScrellingByUser)
mHandler.sendEmptyMessage(0)
}
}
timer.schedule(mTask, BANNER_DELAY_TIME, timeSpace)
}
/**
* 开启轮播图定时器
*/
private fun startBannerScrollTask(timeSpace: Long) {
if (mCount == 0)
return
mTimer = Timer(true)
mTask = object : TimerTask() {
override fun run() {
if (!mIsScrellingByUser)
mHandler.sendEmptyMessage(0)
}
}
mTimer?.schedule(mTask, BANNER_DELAY_TIME, timeSpace)
}
/**
* 关闭轮播图定时器
*/
fun stopBannerTask() {
if (mTask != null)
mTask?.cancel()
}
override fun onPageScrollStateChanged(state: Int) {
mIsScrellingByUser = state != 0
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
mCurrentPos = position
mIndecatorLayout?.getChildAt(mPrePos % mUrls?.size!!)?.isEnabled = false
mIndecatorLayout?.getChildAt(mCurrentPos % mUrls?.size!!)?.isEnabled = true
mPrePos = mCurrentPos
}
class MyAdapter(addUrls: MutableList<String>, ct: Context) : PagerAdapter() {
private var addUrls: MutableList<String>? = addUrls
private var context: Context = ct
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view == `object`
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as View?)
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val img = ImageView(context)
val currentUrl = addUrls?.get(position % addUrls?.size!!)
img.scaleType = ImageView.ScaleType.FIT_XY
Glide.with(context).load(currentUrl).into(img)
container.addView(img)
return img
}
override fun getCount(): Int {
return if (addUrls?.size == 1)
addUrls?.size!!
else
Int.MAX_VALUE
}
}
class MyPageTransformer : ViewPager.PageTransformer {
override fun transformPage(page: View, position: Float) {
val normalizedPosition: Float = Math.abs(Math.abs(position)-1)
page.alpha = normalizedPosition
}
}
}