Android——支持图片加视频无限轮播的banner
基于https://github.com/youth5201314/banner 扩展,图片+视频无限轮播banner
Demo地址
PicVideoBannerDemo: 基于https://github.com/youth5201314/banner扩展,图片+视频无限轮播banner的demo
demo请自行给予应用权限
说明
使用大佬的banner控件是很方便的,但是对视频+图片无限轮播的支持较弱,所以决定重写几个类实现功能。
不重写无法实现的原因如下:
无限轮播模式下,item大于1时, adapter会自动增加两个item,一个在最前,一个在最后,这样子做是为了滑动的视觉效果。
但是这样子也引发了另外一个问题
在onPageSelected回调中,播放第一个或最后一个视频时无法正确的拿到视频控件,导致通过拿到的position播放的那一个item会有声音但无法见到画面
处理播放时是通过adapter保存的ViewHolder对象来进行播放操作,所以使用HashMap保存对应ViewHolder
这是因为实际使用position获取的ViewHolder并不是当前展示的ViewHolder,实际位置应该是 +1 或者 -1
重写几个关键类返回真实position,可以拿到当前正确的ViewHolder,即可解决该问题。
demo效果
依赖
//banner
implementation "com.youth.banner:banner:2.1.0"
//视频播放
implementation 'com.shuyu:GSYVideoPlayer:7.1.3'
主要代码
使用方式
public class MainActivity extends AppCompatActivity {
@BindView(R.id.banner)
ChangeBanner banner;
private List<ResourceBean> dataList;
private MediaVideoBannerAdapter mAdapter;
private BannerVideoManager mBannerVideoManager;
private Unbinder unbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
unbinder = ButterKnife.bind(this);
initDataList();
mAdapter = new MediaVideoBannerAdapter(this, dataList);
banner.isAutoLoop(false);
banner.setAdapter(mAdapter).
setIndicator(new CircleIndicator(this))
.setIndicatorGravity(IndicatorConfig.Direction.CENTER);
mBannerVideoManager = new BannerVideoManager(this, banner, mAdapter, dataList);
mBannerVideoManager.setPageChangeMillis(5000);
mBannerVideoManager.setVideoPlayLoadWait(500);
}
/**
* 数据源请自行替换
* MediaVideoBannerAdapter也需要修改数据类型
*/
private void initDataList(){
//...
}
@Override
protected void onResume() {
super.onResume();
mBannerVideoManager.onResume();
}
@Override
protected void onPause() {
super.onPause();
mBannerVideoManager.onPause();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
mBannerVideoManager.onDetachedFromWindow();
}
@Override
protected void onDestroy() {
super.onDestroy();
unbinder.unbind();
}
}
BaseBannerAdapter
重写一个BannerAdapter
在onBindView(holder, mDatas.get(real), real, getRealCount(), position);中将真实存在的position传出
banner源码中不重写无法在适配器中获取到当前真实的position
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.saqino.picvideobanner.banner.holder.IBaseViewHolder;
import com.youth.banner.listener.OnBannerListener;
import com.youth.banner.util.BannerUtils;
import java.util.ArrayList;
import java.util.List;
public abstract class BaseBannerAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> implements IBaseViewHolder<T, VH> {
protected List<T> mDatas = new ArrayList<>();
private OnBannerListener mOnBannerListener;
private VH mViewHolder;
private int increaseCount = 2;
public BaseBannerAdapter(List<T> datas) {
setDatas(datas);
}
public void setDatas(List<T> datas) {
if (datas == null) {
datas = new ArrayList<>();
}
mDatas = datas;
}
public T getData(int position) {
return mDatas.get(position);
}
@Override
public final void onBindViewHolder(@NonNull VH holder, int position) {
mViewHolder = holder;
int real = getRealPosition(position);
onBindView(holder, mDatas.get(real), real, getRealCount(), position);
if (mOnBannerListener != null)
holder.itemView.setOnClickListener(view -> mOnBannerListener.OnBannerClick(mDatas.get(real), real));
}
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return onCreateHolder(parent, viewType);
}
@Override
public int getItemCount() {
return getRealCount() > 1 ? getRealCount() + increaseCount : getRealCount();
}
public int getRealCount() {
return mDatas == null ? 0 : mDatas.size();
}
public int getRealPosition(int position) {
return BannerUtils.getRealPosition(increaseCount == 2, position, getRealCount());
}
public void setOnBannerListener(OnBannerListener listener) {
this.mOnBannerListener = listener;
}
public VH getViewHolder() {
return mViewHolder;
}
public void setIncreaseCount(int increaseCount) {
this.increaseCount = increaseCount;
}
}
MediaVideoBannerAdapter
继承BaseBannerAdapter并实现图片视频viewholder绑定逻辑
使用HashMap保存每个position对应的ViewHolder,方便取用播放视频
ResourceBean是demo使用的数据类型,根据实际情况修改
import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.saqino.picvideobanner.R;
import com.saqino.picvideobanner.banner.holder.ImageHolder;
import com.saqino.picvideobanner.banner.holder.VideoHolder;
import com.saqino.picvideobanner.bean.ResourceBean;
import com.youth.banner.util.BannerUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MediaVideoBannerAdapter extends BaseBannerAdapter<ResourceBean, RecyclerView.ViewHolder> {
private Context context;
private Map<Integer, VideoHolder> videoHolderMap;
public interface OnBannerVideoPlayListener{
void onVideoPlayStart();
void onVideoPlayComplete();
}
public MediaVideoBannerAdapter(Context context, List<ResourceBean> mDatas) {
super(mDatas);
this.context = context;
videoHolderMap = new HashMap<Integer, VideoHolder>();
}
@Override
public RecyclerView.ViewHolder onCreateHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 1:
return new ImageHolder(BannerUtils.getView(parent, R.layout.banner_image));
case 2:
return new VideoHolder(BannerUtils.getView(parent, R.layout.banner_video));
/*case 3:
return new TitleHolder(BannerUtils.getView(parent, R.layout.banner_title));*/
}
return new ImageHolder(BannerUtils.getView(parent, R.layout.banner_image));
}
@Override
public int getItemViewType(int position) {
return getData(getRealPosition(position)).getType();
}
@Override
public void onBindView(RecyclerView.ViewHolder holder, ResourceBean data, int position, int size, int adapterPosition) {
int viewType = holder.getItemViewType();
switch (viewType) {
case 1:
ImageHolder imageHolder = (ImageHolder) holder;
Glide.with(imageHolder.itemView)
.load(data.getUrl())
.into(imageHolder.imageView);
break;
case 2:
VideoHolder videoHolder = (VideoHolder) holder;
videoHolder.player.setUp(data.getUrl(), true, null);
videoHolder.player.getBackButton().setVisibility(View.GONE);
//增加封面
ImageView imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
//imageView.setImageResource(R.drawable.image4);
videoHolder.player.setThumbImageView(imageView);
videoHolderMap.put(adapterPosition, videoHolder);
break;
}
}
public VideoHolder getVideoHolder(Integer position){
return videoHolderMap.get(position);
}
public void stopVideo(){
for (Integer key : videoHolderMap.keySet()){
try {
if (getVideoHolder(key) != null) {
getVideoHolder(key).player.release();
getVideoHolder(key).player.onVideoReset();
}
}catch (Throwable e){
Log.e("VideoBannerError", "停止视频播放出错 "+e.getLocalizedMessage());
}
}
}
}
ChangeBanner
修改后的Banner控件,主要是修改BannerAdapter的类型
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
import androidx.viewpager2.widget.CompositePageTransformer;
import androidx.viewpager2.widget.MarginPageTransformer;
import androidx.viewpager2.widget.ViewPager2;
import com.saqino.picvideobanner.R;
import com.saqino.picvideobanner.banner.adapter.BaseBannerAdapter;
import com.saqino.picvideobanner.banner.manager.ScrollSpeedManger;
import com.youth.banner.config.BannerConfig;
import com.youth.banner.config.IndicatorConfig;
import com.youth.banner.indicator.Indicator;
import com.youth.banner.listener.OnBannerListener;
import com.youth.banner.listener.OnPageChangeListener;
import com.youth.banner.transformer.MZScaleInTransformer;
import com.youth.banner.transformer.ScaleInTransformer;
import com.youth.banner.util.BannerLifecycleObserver;
import com.youth.banner.util.BannerLifecycleObserverAdapter;
import com.youth.banner.util.BannerUtils;
import com.youth.banner.util.LogUtils;
import java.lang.annotation.Retention;
import java.lang.ref.WeakReference;
import java.util.List;
import static java.lang.annotation.RetentionPolicy.SOURCE;
public class ChangeBanner<T, BA extends BaseBannerAdapter> extends FrameLayout implements BannerLifecycleObserver {
public static final int INVALID_VALUE = -1;
private ViewPager2 mViewPager2;
private AutoLoopTask mLoopTask;
private OnPageChangeListener mOnPageChangeListener;
private BA mAdapter;
private Indicator mIndicator;
private CompositePageTransformer mCompositePageTransformer;
private BannerOnPageChangeCallback mPageChangeCallback;
// 是否允许无限轮播(即首尾直接切换)
private boolean mIsInfiniteLoop = BannerConfig.IS_INFINITE_LOOP;
// 是否自动轮播
private boolean mIsAutoLoop = BannerConfig.IS_AUTO_LOOP;
// 轮播切换间隔时间
private long mLoopTime = BannerConfig.LOOP_TIME;
// 轮播切换时间
private int mScrollTime = BannerConfig.SCROLL_TIME;
// 轮播开始位置
private int mStartPosition = 1;
// banner圆角半径
private float mBannerRadius = 0;
// 指示器相关配置
private int normalWidth = BannerConfig.INDICATOR_NORMAL_WIDTH;
private int selectedWidth = BannerConfig.INDICATOR_SELECTED_WIDTH;
private int normalColor = BannerConfig.INDICATOR_NORMAL_COLOR;
private int selectedColor = BannerConfig.INDICATOR_SELECTED_COLOR;
private int indicatorGravity = IndicatorConfig.Direction.CENTER;
private int indicatorSpace;
private int indicatorMargin;
private int indicatorMarginLeft;
private int indicatorMarginTop;
private int indicatorMarginRight;
private int indicatorMarginBottom;
private int indicatorHeight = BannerConfig.INDICATOR_HEIGHT;
private int indicatorRadius = BannerConfig.INDICATOR_RADIUS;
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
// 滑动距离范围
private int mTouchSlop;
// 记录触摸的位置(主要用于解决事件冲突问题)
private float mStartX, mStartY;
// 记录viewpager2是否被拖动
private boolean mIsViewPager2Drag;
// 是否要拦截事件
private boolean isIntercept = true;
//绘制圆角视图
private Paint mRoundPaint;
private Paint mImagePaint;
@Retention(SOURCE)
@IntDef( {HORIZONTAL, VERTICAL})
public @interface Orientation {
}
public ChangeBanner(Context context) {
this(context, null);
}
public ChangeBanner(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ChangeBanner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
initTypedArray(context, attrs);
}
private void init(Context context) {
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop() / 2;
mCompositePageTransformer = new CompositePageTransformer();
mPageChangeCallback = new BannerOnPageChangeCallback();
mLoopTask = new AutoLoopTask(this);
mViewPager2 = new ViewPager2(context);
mViewPager2.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mViewPager2.setOffscreenPageLimit(1);
mViewPager2.registerOnPageChangeCallback(mPageChangeCallback);
mViewPager2.setPageTransformer(mCompositePageTransformer);
ScrollSpeedManger.reflectLayoutManager(this);
addView(mViewPager2);
mRoundPaint = new Paint();
mRoundPaint.setColor(Color.WHITE);
mRoundPaint.setAntiAlias(true);
mRoundPaint.setStyle(Paint.Style.FILL);
mRoundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mImagePaint = new Paint();
mImagePaint.setXfermode(null);
}
private void initTypedArray(Context context, AttributeSet attrs) {
if (attrs == null) {
return;
}
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Banner);
mBannerRadius = a.getDimensionPixelSize(R.styleable.Banner_banner_radius, 0);
mLoopTime = a.getInt(R.styleable.Banner_banner_loop_time, BannerConfig.LOOP_TIME);
mIsAutoLoop = a.getBoolean(R.styleable.Banner_banner_auto_loop, BannerConfig.IS_AUTO_LOOP);
mIsInfiniteLoop = a.getBoolean(R.styleable.Banner_banner_infinite_loop, BannerConfig.IS_INFINITE_LOOP);
normalWidth = a.getDimensionPixelSize(R.styleable.Banner_banner_indicator_normal_width, BannerConfig.INDICATOR_NORMAL_WIDTH);
selectedWidth = a.getDimensionPixelSize(R.styleable.Banner_banner_indicator_selected_width, BannerConfig.INDICATOR_SELECTED_WIDTH);
normalColor = a.getColor(R.styleable.Banner_banner_indicator_normal_color, BannerConfig.INDICATOR_NORMAL_COLOR);
selectedColor = a.getColor(R.styleable.Banner_banner_indicator_selected_color, BannerConfig.INDICATOR_SELECTED_COLOR);
indicatorGravity = a.getInt(R.styleable.Banner_banner_indicator_gravity, IndicatorConfig.Direction.CENTER);
indicatorSpace = a.getDimensionPixelSize(R.styleable.Banner_banner_indicator_space, 0);
indicatorMargin = a.getDimensionPixelSize(R.styleable.Banner_banner_indicator_margin, 0);
indicatorMarginLeft = a.getDimensionPixelSize(R.styleable.Banner_banner_indicator_marginLeft, 0);
indicatorMarginTop = a.getDimensionPixelSize(R.styleable.Banner_banner_indicator_marginTop, 0);
indicatorMarginRight = a.getDimensionPixelSize(R.styleable.Banner_banner_indicator_marginRight, 0);
indicatorMarginBottom = a.getDimensionPixelSize(R.styleable.Banner_banner_indicator_marginBottom, 0);
indicatorHeight = a.getDimensionPixelSize(R.styleable.Banner_banner_indicator_height, BannerConfig.INDICATOR_HEIGHT);
indicatorRadius = a.getDimensionPixelSize(R.styleable.Banner_banner_indicator_radius, BannerConfig.INDICATOR_RADIUS);
int orientation = a.getInt(R.styleable.Banner_banner_orientation, HORIZONTAL);
setOrientation(orientation);
setInfiniteLoop();
a.recycle();
}
private void initIndicatorAttr() {
if (indicatorMargin != 0) {
setIndicatorMargins(new IndicatorConfig.Margins(indicatorMargin));
} else if (indicatorMarginLeft != 0
|| indicatorMarginTop != 0
|| indicatorMarginRight != 0
|| indicatorMarginBottom != 0) {
setIndicatorMargins(new IndicatorConfig.Margins(
indicatorMarginLeft,
indicatorMarginTop,
indicatorMarginRight,
indicatorMarginBottom));
}
if (indicatorSpace > 0) {
setIndicatorSpace(indicatorSpace);
}
if (indicatorGravity != IndicatorConfig.Direction.CENTER) {
setIndicatorGravity(indicatorGravity);
}
if (normalWidth > 0) {
setIndicatorNormalWidth(normalWidth);
}
if (selectedWidth > 0) {
setIndicatorSelectedWidth(selectedWidth);
}
if (indicatorHeight > 0) {
setIndicatorHeight(indicatorHeight);
}
if (indicatorRadius > 0) {
setIndicatorRadius(indicatorRadius);
}
setIndicatorNormalColor(normalColor);
setIndicatorSelectedColor(selectedColor);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!getViewPager2().isUserInputEnabled()) {
return super.dispatchTouchEvent(ev);
}
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_OUTSIDE) {
start();
} else if (action == MotionEvent.ACTION_DOWN) {
stop();
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (!getViewPager2().isUserInputEnabled() || !isIntercept) {
return super.onInterceptTouchEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartX = event.getX();
mStartY = event.getY();
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
float endX = event.getX();
float endY = event.getY();
float distanceX = Math.abs(endX - mStartX);
float distanceY = Math.abs(endY - mStartY);
if (getViewPager2().getOrientation() == HORIZONTAL) {
mIsViewPager2Drag = distanceX > mTouchSlop && distanceX > distanceY;
} else {
mIsViewPager2Drag = distanceY > mTouchSlop && distanceY > distanceX;
}
getParent().requestDisallowInterceptTouchEvent(mIsViewPager2Drag);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return super.onInterceptTouchEvent(event);
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (mBannerRadius > 0) {
canvas.saveLayer(new RectF(0, 0, canvas.getWidth(), canvas.getHeight()), mImagePaint, Canvas.ALL_SAVE_FLAG);
super.dispatchDraw(canvas);
//绘制外圆环边框圆环
drawTopLeft(canvas);
drawTopRight(canvas);
drawBottomLeft(canvas);
drawBottomRight(canvas);
canvas.restore();
} else {
super.dispatchDraw(canvas);
}
}
private void drawTopLeft(Canvas canvas) {
Path path = new Path();
path.moveTo(0, mBannerRadius);
path.lineTo(0, 0);
path.lineTo(mBannerRadius, 0);
path.arcTo(new RectF(0, 0, mBannerRadius * 2, mBannerRadius * 2),
-90, -90);
path.close();
canvas.drawPath(path, mRoundPaint);
}
private void drawTopRight(Canvas canvas) {
int width = getWidth();
Path path = new Path();
path.moveTo(width - mBannerRadius, 0);
path.lineTo(width, 0);
path.lineTo(width, mBannerRadius);
path.arcTo(new RectF(width - 2 * mBannerRadius, 0, width,
mBannerRadius * 2), 0, -90);
path.close();
canvas.drawPath(path, mRoundPaint);
}
private void drawBottomLeft(Canvas canvas) {
int height = getHeight();
Path path = new Path();
path.moveTo(0, height - mBannerRadius);
path.lineTo(0, height);
path.lineTo(mBannerRadius, height);
path.arcTo(new RectF(0, height - 2 * mBannerRadius,
mBannerRadius * 2, height), 90, 90);
path.close();
canvas.drawPath(path, mRoundPaint);
}
private void drawBottomRight(Canvas canvas) {
int height = getHeight();
int width = getWidth();
Path path = new Path();
path.moveTo(width - mBannerRadius, height);
path.lineTo(width, height);
path.lineTo(width, height - mBannerRadius);
path.arcTo(new RectF(width - 2 * mBannerRadius, height - 2
* mBannerRadius, width, height), 0, 90);
path.close();
canvas.drawPath(path, mRoundPaint);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
start();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stop();
}
class BannerOnPageChangeCallback extends ViewPager2.OnPageChangeCallback {
private int mTempPosition = INVALID_VALUE;
private boolean isScrolled;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int realPosition = BannerUtils.getRealPosition(isInfiniteLoop(), position, getRealCount());
if (mOnPageChangeListener != null) {
mOnPageChangeListener.onPageScrolled(realPosition, positionOffset, positionOffsetPixels);
}
if (mIndicator != null) {
mIndicator.onPageScrolled(realPosition, positionOffset, positionOffsetPixels);
}
}
@Override
public void onPageSelected(int position) {
if (isScrolled) {
mTempPosition = position;
int realPosition = BannerUtils.getRealPosition(isInfiniteLoop(), position, getRealCount());
if (mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(realPosition);
}
if (mIndicator != null) {
mIndicator.onPageSelected(realPosition);
}
}
}
@Override
public void onPageScrollStateChanged(int state) {
//手势滑动中,代码执行滑动中
if (state == ViewPager2.SCROLL_STATE_DRAGGING || state == ViewPager2.SCROLL_STATE_SETTLING) {
isScrolled = true;
} else if (state == ViewPager2.SCROLL_STATE_IDLE) {
//滑动闲置或滑动结束
isScrolled = false;
if (mTempPosition != INVALID_VALUE && mIsInfiniteLoop) {
if (mTempPosition == 0) {
setCurrentItem(getRealCount(), false);
} else if (mTempPosition == getItemCount() - 1) {
setCurrentItem(1, false);
}
}
}
if (mOnPageChangeListener != null) {
mOnPageChangeListener.onPageScrollStateChanged(state);
}
if (mIndicator != null) {
mIndicator.onPageScrollStateChanged(state);
}
}
}
static class AutoLoopTask implements Runnable {
private final WeakReference<ChangeBanner> reference;
AutoLoopTask(ChangeBanner banner) {
this.reference = new WeakReference<>(banner);
}
@Override
public void run() {
ChangeBanner banner = reference.get();
if (banner != null && banner.mIsAutoLoop) {
int count = banner.getItemCount();
if (count == 0) {
return;
}
int next = (banner.getCurrentItem() + 1) % count;
banner.setCurrentItem(next);
banner.postDelayed(banner.mLoopTask, banner.mLoopTime);
}
}
}
private RecyclerView.AdapterDataObserver mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
if (getItemCount() <= 1) {
stop();
} else {
start();
}
setIndicatorPageChange();
}
};
private void initIndicator() {
if (mIndicator == null || getAdapter() == null) {
return;
}
if (mIndicator.getIndicatorConfig().isAttachToBanner()) {
removeIndicator();
addView(mIndicator.getIndicatorView());
}
initIndicatorAttr();
setIndicatorPageChange();
}
private void setInfiniteLoop() {
// 当不支持无限循环时,要关闭自动轮播
if (!isInfiniteLoop()) {
isAutoLoop(false);
}
setStartPosition(isInfiniteLoop() ? 1 : 0);
}
private void setRecyclerViewPadding(int itemPadding) {
setRecyclerViewPadding(itemPadding, itemPadding);
}
private void setRecyclerViewPadding(int leftItemPadding, int rightItemPadding) {
RecyclerView recyclerView = (RecyclerView) getViewPager2().getChildAt(0);
if (getViewPager2().getOrientation() == ViewPager2.ORIENTATION_VERTICAL) {
recyclerView.setPadding(0, leftItemPadding, 0, rightItemPadding);
} else {
recyclerView.setPadding(leftItemPadding, 0, rightItemPadding, 0);
}
recyclerView.setClipToPadding(false);
}
/**
* **********************************************************************
* ------------------------ 对外公开API ---------------------------------*
* **********************************************************************
*/
public int getCurrentItem() {
return getViewPager2().getCurrentItem();
}
public int getItemCount() {
if (getAdapter() == null) {
return 0;
}
return getAdapter().getItemCount();
}
public int getScrollTime() {
return mScrollTime;
}
public boolean isInfiniteLoop() {
return mIsInfiniteLoop;
}
public BA getAdapter() {
if (mAdapter == null) {
LogUtils.e(getContext().getString(R.string.banner_adapter_use_error));
}
return mAdapter;
}
public ViewPager2 getViewPager2() {
return mViewPager2;
}
public Indicator getIndicator() {
if (mIndicator == null) {
LogUtils.e(getContext().getString(R.string.indicator_null_error));
}
return mIndicator;
}
public IndicatorConfig getIndicatorConfig() {
if (getIndicator() != null) {
return getIndicator().getIndicatorConfig();
}
return null;
}
/**
* 返回banner真实总数
*/
public int getRealCount() {
return getAdapter().getRealCount();
}
//-----------------------------------------------------------------------------------------
/**
* 是否要拦截事件
* @param intercept
* @return
*/
public ChangeBanner setIntercept(boolean intercept) {
isIntercept = intercept;
return this;
}
/**
* 跳转到指定位置(最好在设置了数据后在调用,不然没有意义)
* @param position
* @return
*/
public ChangeBanner setCurrentItem(int position) {
return setCurrentItem(position, true);
}
/**
* 跳转到指定位置(最好在设置了数据后在调用,不然没有意义)
* @param position
* @param smoothScroll
* @return
*/
public ChangeBanner setCurrentItem(int position, boolean smoothScroll) {
getViewPager2().setCurrentItem(position, smoothScroll);
return this;
}
public ChangeBanner setIndicatorPageChange() {
if (mIndicator != null) {
int realPosition = BannerUtils.getRealPosition(isInfiniteLoop(), getCurrentItem(), getRealCount());
mIndicator.onPageChanged(getRealCount(), realPosition);
}
return this;
}
public ChangeBanner removeIndicator() {
if (mIndicator != null) {
removeView(mIndicator.getIndicatorView());
}
return this;
}
/**
* 设置开始的位置 (需要在setAdapter或者setDatas之前调用才有效哦)
*/
public ChangeBanner setStartPosition(int mStartPosition) {
this.mStartPosition = mStartPosition;
return this;
}
/**
* 禁止手动滑动
*
* @param enabled true 允许,false 禁止
*/
public ChangeBanner setUserInputEnabled(boolean enabled) {
getViewPager2().setUserInputEnabled(enabled);
return this;
}
/**
* 添加PageTransformer,可以组合效果
* {@link ViewPager2.PageTransformer}
* 如果找不到请导入implementation "androidx.viewpager2:viewpager2:1.0.0"
*/
public ChangeBanner addPageTransformer(@Nullable ViewPager2.PageTransformer transformer) {
mCompositePageTransformer.addTransformer(transformer);
return this;
}
/**
* 设置PageTransformer,和addPageTransformer不同,这个只支持一种transformer
*/
public ChangeBanner setPageTransformer(@Nullable ViewPager2.PageTransformer transformer) {
getViewPager2().setPageTransformer(transformer);
return this;
}
public ChangeBanner removeTransformer(ViewPager2.PageTransformer transformer) {
mCompositePageTransformer.removeTransformer(transformer);
return this;
}
/**
* 添加 ItemDecoration
*/
public ChangeBanner addItemDecoration(RecyclerView.ItemDecoration decor) {
getViewPager2().addItemDecoration(decor);
return this;
}
public ChangeBanner addItemDecoration(RecyclerView.ItemDecoration decor, int index) {
getViewPager2().addItemDecoration(decor, index);
return this;
}
/**
* 是否允许自动轮播
*
* @param isAutoLoop ture 允许,false 不允许
*/
public ChangeBanner isAutoLoop(boolean isAutoLoop) {
this.mIsAutoLoop = isAutoLoop;
return this;
}
/**
* 设置轮播间隔时间
*
* @param loopTime 时间(毫秒)
*/
public ChangeBanner setLoopTime(long loopTime) {
this.mLoopTime = loopTime;
return this;
}
/**
* 设置轮播滑动过程的时间
*/
public ChangeBanner setScrollTime(int scrollTime) {
this.mScrollTime = scrollTime;
return this;
}
/**
* 开始轮播
*/
public ChangeBanner start() {
if (mIsAutoLoop) {
stop();
postDelayed(mLoopTask, mLoopTime);
}
return this;
}
/**
* 停止轮播
*/
public ChangeBanner stop() {
if (mIsAutoLoop) {
removeCallbacks(mLoopTask);
}
return this;
}
/**
* 移除一些引用
*/
public void destroy() {
if (getViewPager2() != null && mPageChangeCallback != null) {
getViewPager2().unregisterOnPageChangeCallback(mPageChangeCallback);
mPageChangeCallback = null;
}
stop();
}
/**
* 设置banner的适配器
*/
public ChangeBanner setAdapter(BA adapter) {
if (adapter == null) {
throw new NullPointerException(getContext().getString(R.string.banner_adapter_null_error));
}
this.mAdapter = adapter;
if (!isInfiniteLoop()) {
mAdapter.setIncreaseCount(0);
}
mAdapter.registerAdapterDataObserver(mAdapterDataObserver);
mViewPager2.setAdapter(adapter);
setCurrentItem(mStartPosition, false);
initIndicator();
return this;
}
/**
* 设置banner的适配器
* @param adapter
* @param isInfiniteLoop 是否支持无限循环
* @return
*/
public ChangeBanner setAdapter(BA adapter,boolean isInfiniteLoop) {
mIsInfiniteLoop=isInfiniteLoop;
setInfiniteLoop();
setAdapter(adapter);
return this;
}
/**
* 重新设置banner数据,当然你也可以在你adapter中自己操作数据,不要过于局限在这个方法,举一反三哈
*
* @param datas 数据集合,当传null或者datas没有数据时,banner会变成空白的,请做好占位UI处理
*/
public ChangeBanner setDatas(List<T> datas) {
if (getAdapter() != null) {
getAdapter().setDatas(datas);
getAdapter().notifyDataSetChanged();
setCurrentItem(mStartPosition, false);
setIndicatorPageChange();
start();
}
return this;
}
/**
* 设置banner轮播方向
*
* @param orientation {@link Orientation}
*/
public ChangeBanner setOrientation(@Orientation int orientation) {
getViewPager2().setOrientation(orientation);
return this;
}
/**
* 改变最小滑动距离
*/
public ChangeBanner setTouchSlop(int mTouchSlop) {
this.mTouchSlop = mTouchSlop;
return this;
}
/**
* 设置点击事件
*/
public ChangeBanner setOnBannerListener(OnBannerListener listener) {
if (getAdapter() != null) {
getAdapter().setOnBannerListener(listener);
}
return this;
}
/**
* 添加viewpager切换事件
* <p>
* 在viewpager2中切换事件{@link ViewPager2.OnPageChangeCallback}是一个抽象类,
* 为了方便使用习惯这里用的是和viewpager一样的{@link ViewPager.OnPageChangeListener}接口
* </p>
*/
public ChangeBanner addOnPageChangeListener(OnPageChangeListener pageListener) {
this.mOnPageChangeListener = pageListener;
return this;
}
/**
* 设置banner圆角
* <p>
* 默认没有圆角,需要取消圆角把半径设置为0即可
*
* @param radius 圆角半径
*/
public ChangeBanner setBannerRound(float radius) {
mBannerRadius = radius;
return this;
}
/**
* 设置banner圆角(第二种方式,和上面的方法不要同时使用),只支持5.0以上
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public ChangeBanner setBannerRound2(float radius) {
BannerUtils.setBannerRound(this, radius);
return this;
}
/**
* 为banner添加画廊效果
*
* @param itemWidth item左右展示的宽度,单位dp
* @param pageMargin 页面间距,单位dp
*/
public ChangeBanner setBannerGalleryEffect(int itemWidth, int pageMargin) {
return setBannerGalleryEffect(itemWidth, pageMargin, .85f);
}
/**
* 为banner添加画廊效果
*
* @param leftItemWidth item左展示的宽度,单位dp
* @param rightItemWidth item右展示的宽度,单位dp
* @param pageMargin 页面间距,单位dp
*/
public ChangeBanner setBannerGalleryEffect(int leftItemWidth, int rightItemWidth, int pageMargin) {
return setBannerGalleryEffect(leftItemWidth,rightItemWidth, pageMargin, .85f);
}
/**
* 为banner添加画廊效果
*
* @param itemWidth item左右展示的宽度,单位dp
* @param pageMargin 页面间距,单位dp
* @param scale 缩放[0-1],1代表不缩放
*/
public ChangeBanner setBannerGalleryEffect(int itemWidth, int pageMargin, float scale) {
return setBannerGalleryEffect(itemWidth, itemWidth, pageMargin, scale);
}
/**
* 为banner添加画廊效果
*
* @param leftItemWidth item左展示的宽度,单位dp
* @param rightItemWidth item右展示的宽度,单位dp
* @param pageMargin 页面间距,单位dp
* @param scale 缩放[0-1],1代表不缩放
*/
public ChangeBanner setBannerGalleryEffect(int leftItemWidth, int rightItemWidth, int pageMargin, float scale) {
if (pageMargin > 0) {
addPageTransformer(new MarginPageTransformer((int) BannerUtils.dp2px(pageMargin)));
}
if (scale < 1 && scale > 0) {
addPageTransformer(new ScaleInTransformer(scale));
}
setRecyclerViewPadding(leftItemWidth > 0 ? (int) BannerUtils.dp2px(leftItemWidth + pageMargin) : 0,
rightItemWidth > 0 ? (int) BannerUtils.dp2px(rightItemWidth + pageMargin) : 0);
return this;
}
/**
* 为banner添加魅族效果
*
* @param itemWidth item左右展示的宽度,单位dp
*/
public ChangeBanner setBannerGalleryMZ(int itemWidth) {
return setBannerGalleryMZ(itemWidth, .88f);
}
/**
* 为banner添加魅族效果
*
* @param itemWidth item左右展示的宽度,单位dp
* @param scale 缩放[0-1],1代表不缩放
*/
public ChangeBanner setBannerGalleryMZ(int itemWidth, float scale) {
if (scale < 1 && scale > 0) {
addPageTransformer(new MZScaleInTransformer(scale));
}
setRecyclerViewPadding((int) BannerUtils.dp2px(itemWidth));
return this;
}
/**
* **********************************************************************
* ------------------------ 指示器相关设置 --------------------------------*
* **********************************************************************
*/
/**
* 设置轮播指示器(显示在banner上)
*/
public ChangeBanner setIndicator(Indicator indicator) {
return setIndicator(indicator, true);
}
/**
* 设置轮播指示器(如果你的指示器写在布局文件中,attachToBanner传false)
*
* @param attachToBanner 是否将指示器添加到banner中,false 代表你可以将指示器通过布局放在任何位置
* 注意:设置为false后,内置的 setIndicatorGravity()和setIndicatorMargins() 方法将失效。
* 想改变可以自己调用系统提供的属性在布局文件中进行设置。具体可以参照demo
*/
public ChangeBanner setIndicator(Indicator indicator, boolean attachToBanner) {
removeIndicator();
indicator.getIndicatorConfig().setAttachToBanner(attachToBanner);
this.mIndicator = indicator;
initIndicator();
return this;
}
public ChangeBanner setIndicatorSelectedColor(@ColorInt int color) {
if (mIndicator != null) {
mIndicator.getIndicatorConfig().setSelectedColor(color);
}
return this;
}
public ChangeBanner setIndicatorSelectedColorRes(@ColorRes int color) {
setIndicatorSelectedColor(ContextCompat.getColor(getContext(), color));
return this;
}
public ChangeBanner setIndicatorNormalColor(@ColorInt int color) {
if (mIndicator != null) {
mIndicator.getIndicatorConfig().setNormalColor(color);
}
return this;
}
public ChangeBanner setIndicatorNormalColorRes(@ColorRes int color) {
setIndicatorNormalColor(ContextCompat.getColor(getContext(), color));
return this;
}
public ChangeBanner setIndicatorGravity(@IndicatorConfig.Direction int gravity) {
if (mIndicator != null && mIndicator.getIndicatorConfig().isAttachToBanner()) {
mIndicator.getIndicatorConfig().setGravity(gravity);
mIndicator.getIndicatorView().postInvalidate();
}
return this;
}
public ChangeBanner setIndicatorSpace(int indicatorSpace) {
if (mIndicator != null) {
mIndicator.getIndicatorConfig().setIndicatorSpace(indicatorSpace);
}
return this;
}
public ChangeBanner setIndicatorMargins(IndicatorConfig.Margins margins) {
if (mIndicator != null && mIndicator.getIndicatorConfig().isAttachToBanner()) {
mIndicator.getIndicatorConfig().setMargins(margins);
mIndicator.getIndicatorView().requestLayout();
}
return this;
}
public ChangeBanner setIndicatorWidth(int normalWidth, int selectedWidth) {
if (mIndicator != null) {
mIndicator.getIndicatorConfig().setNormalWidth(normalWidth);
mIndicator.getIndicatorConfig().setSelectedWidth(selectedWidth);
}
return this;
}
public ChangeBanner setIndicatorNormalWidth(int normalWidth) {
if (mIndicator != null) {
mIndicator.getIndicatorConfig().setNormalWidth(normalWidth);
}
return this;
}
public ChangeBanner setIndicatorSelectedWidth(int selectedWidth) {
if (mIndicator != null) {
mIndicator.getIndicatorConfig().setSelectedWidth(selectedWidth);
}
return this;
}
public ChangeBanner<T, BA> setIndicatorRadius(int indicatorRadius) {
if (mIndicator != null) {
mIndicator.getIndicatorConfig().setRadius(indicatorRadius);
}
return this;
}
public ChangeBanner<T, BA> setIndicatorHeight(int indicatorHeight) {
if (mIndicator != null) {
mIndicator.getIndicatorConfig().setHeight(indicatorHeight);
}
return this;
}
/**
* **********************************************************************
* ------------------------ 生命周期控制 --------------------------------*
* **********************************************************************
*/
public ChangeBanner addBannerLifecycleObserver(LifecycleOwner owner) {
if (owner != null) {
owner.getLifecycle().addObserver(new BannerLifecycleObserverAdapter(owner, this));
}
return this;
}
@Override
public void onStart(LifecycleOwner owner) {
start();
}
@Override
public void onStop(LifecycleOwner owner) {
stop();
}
@Override
public void onDestroy(LifecycleOwner owner) {
destroy();
}
}
manager相关类
用于处理轮播及Activity生命周期相关的事件
BannerVideoManager
import android.content.Context;
import android.os.Handler;
import android.widget.Toast;
import com.saqino.picvideobanner.banner.ChangeBanner;
import com.saqino.picvideobanner.banner.adapter.MediaVideoBannerAdapter;
import com.saqino.picvideobanner.banner.holder.VideoHolder;
import com.saqino.picvideobanner.bean.ResourceBean;
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack;
import com.youth.banner.listener.OnPageChangeListener;
import java.util.List;
public class BannerVideoManager {
private Context context;
private ChangeBanner banner;
private MediaVideoBannerAdapter adapter;
//数据源,请自行替换
private List<ResourceBean> list;
private long mPageChangeMillis = 5000;
private long mVideoPlayLoadWait = 500;
public BannerVideoManager(Context context, ChangeBanner banner,
MediaVideoBannerAdapter adapter, List<ResourceBean> list){
this.context = context;
this.banner = banner;
this.adapter = adapter;
this.list = list;
banner.addOnPageChangeListener(new BannerPageChange());
}
//设置切换间隔时间,单位毫秒
public void setPageChangeMillis(long mills){
this.mPageChangeMillis = mills;
}
/**
* 设置视频播放前置等待时间,单位毫秒
*
* 设置为无限轮播时,适配器中会多出两个ViewHolder
* 需要给banner自动切换ViewHolder一点时间
* 保证获取到的ViewHolder是当前展示的那一个
*
* @param mills
*/
public void setVideoPlayLoadWait(long mills){
this.mVideoPlayLoadWait = mills;
}
/**
* 视频+图片轮播处理代码
*/
private VideoHolder currentVideoHolder = null;
private class BannerPageChange implements OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mPosition = position;
if (adapter != null){
currentVideoHolder = null;
//当前页不是视频类型
if (list.get(position).getType() != 2) {
adapter.stopVideo();
if (videoPlayRunnable != null) mHandler.removeCallbacks(videoPlayRunnable);
startScroll(mPageChangeMillis);
}else {
//视频类型,且仅有一个视频,自动重播
if (list.size() == 1)
playVideo(0, position);
else {
playVideo(mVideoPlayLoadWait, position+1);
}
stopScroll();
}
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
}
//当前position(显示出来的position,非真实position
private int mPosition = 0;
private Handler mHandler = new Handler();
//轮播切换的runnable
private Runnable runnable = new Runnable() {
@Override
public void run() {
mHandler.removeCallbacks(runnable);
banner.setCurrentItem(banner.getCurrentItem() + 1, true);
}
};
//控制视频播放
private Runnable videoPlayRunnable;
private void playVideo(long millis, int position){
if (videoPlayRunnable != null) mHandler.removeCallbacks(videoPlayRunnable);
videoPlayRunnable = new Runnable(){
@Override
public void run() {
if (position == 1 && list.size() > 1 && banner.getCurrentItem() != 1){
banner.setCurrentItem(1, false);
return;
}
VideoHolder videoHolder = adapter.getVideoHolder(position);
if (videoHolder == null) {
showToast("获取视频播放控件出错");
return;
}
videoHolder.player.onVideoReset();
videoHolder.player.startPlayLogic();
videoHolder.player.setVideoAllCallBack(new GSYSampleCallBack(){
@Override
public void onAutoComplete(String url, Object... objects) {
super.onAutoComplete(url, objects);
if (position == 0) videoHolder.player.startPlayLogic();
else startScroll(0);
}
});
currentVideoHolder = videoHolder;
}
};
mHandler.postDelayed(videoPlayRunnable, millis);
}
//开始切换
private void startScroll(long millis){
mHandler.postDelayed(runnable, millis);
}
//停止切换
private void stopScroll(){
mHandler.removeCallbacks(runnable);
}
private void showToast(String str){
Toast.makeText(context, str, Toast.LENGTH_SHORT).show();
}
//Activity生命周期控制
public void onPause(){
stopScroll();
if (currentVideoHolder != null) currentVideoHolder.player.onVideoPause();
mHandler.removeCallbacks(videoPlayRunnable);
}
//Activity生命周期控制
public void onResume(){
if (list == null) return;
if (list.size() == 0) return;
if (list.get(mPosition).getType() == 2) {
if (currentVideoHolder != null) currentVideoHolder.player.onVideoResume();
else {
if (list.size() == 1)
playVideo(mVideoPlayLoadWait, mPosition);
else playVideo(mVideoPlayLoadWait, mPosition+1);
}
}else startScroll(mPageChangeMillis);
}
//Activity生命周期控制
public void onDetachedFromWindow(){
stopScroll();
}
}
ScrollSpeedManger
原项目中的一个管理类,修改了adapter的类型
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import com.saqino.picvideobanner.banner.ChangeBanner;
import java.lang.reflect.Field;
/**
* 改变LinearLayoutManager的切换速度
*/
public class ScrollSpeedManger extends LinearLayoutManager {
private ChangeBanner banner;
public ScrollSpeedManger(ChangeBanner banner, LinearLayoutManager linearLayoutManager) {
super(banner.getContext(), linearLayoutManager.getOrientation(), false);
this.banner = banner;
}
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
@Override
protected int calculateTimeForDeceleration(int dx) {
return banner.getScrollTime();
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
public static void reflectLayoutManager(ChangeBanner banner) {
if (banner.getScrollTime() < 100) return;
try {
ViewPager2 viewPager2 = banner.getViewPager2();
RecyclerView recyclerView = (RecyclerView) viewPager2.getChildAt(0);
recyclerView.setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER);
ScrollSpeedManger speedManger = new ScrollSpeedManger(banner, (LinearLayoutManager) recyclerView.getLayoutManager());
recyclerView.setLayoutManager(speedManger);
Field LayoutMangerField = ViewPager2.class.getDeclaredField("mLayoutManager");
LayoutMangerField.setAccessible(true);
LayoutMangerField.set(viewPager2, speedManger);
Field pageTransformerAdapterField = ViewPager2.class.getDeclaredField("mPageTransformerAdapter");
pageTransformerAdapterField.setAccessible(true);
Object mPageTransformerAdapter = pageTransformerAdapterField.get(viewPager2);
if (mPageTransformerAdapter != null) {
Class<?> aClass = mPageTransformerAdapter.getClass();
Field layoutManager = aClass.getDeclaredField("mLayoutManager");
layoutManager.setAccessible(true);
layoutManager.set(mPageTransformerAdapter, speedManger);
}
Field scrollEventAdapterField = ViewPager2.class.getDeclaredField("mScrollEventAdapter");
scrollEventAdapterField.setAccessible(true);
Object mScrollEventAdapter = scrollEventAdapterField.get(viewPager2);
if (mScrollEventAdapter != null) {
Class<?> aClass = mScrollEventAdapter.getClass();
Field layoutManager = aClass.getDeclaredField("mLayoutManager");
layoutManager.setAccessible(true);
layoutManager.set(mScrollEventAdapter, speedManger);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Holder类
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.saqino.picvideobanner.R;
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer;
public class VideoHolder extends RecyclerView.ViewHolder {
public StandardGSYVideoPlayer player;
public VideoHolder(@NonNull View view) {
super(view);
player = view.findViewById(R.id.player);
}
}
import android.view.ViewGroup;
public interface IBaseViewHolder<T, VH> {
/**
* 创建ViewHolder
*
* @return XViewHolder
*/
VH onCreateHolder(ViewGroup parent, int viewType);
/**
* 绑定布局数据
*
* @param holder XViewHolder
* @param data 数据实体
* @param position 当前位置
* @param size 总数
*/
void onBindView(VH holder, T data, int position, int size, int adapterPosition);
}
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class ImageHolder extends RecyclerView.ViewHolder {
public ImageView imageView;
public ImageHolder(@NonNull View view) {
super(view);
this.imageView = (ImageView) view;
}
}
更多推荐
所有评论(0)