Android——支持图片加视频无限轮播的banner
基于https://github.com/youth5201314/banner 扩展,图片+视频无限轮播banner
Demo地址
https://gitee.com/saqino/pic-video-banner-demo.git
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;@Overrideprotected 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(){//...}@Overrideprotected void onResume() {super.onResume();mBannerVideoManager.onResume();}@Overrideprotected void onPause() {super.onPause();mBannerVideoManager.onPause();}@Overridepublic void onDetachedFromWindow() {super.onDetachedFromWindow();mBannerVideoManager.onDetachedFromWindow();}@Overrideprotected 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);}@Overridepublic 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@Overridepublic VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {return onCreateHolder(parent, viewType);}@Overridepublic 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>();}@Overridepublic 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));}@Overridepublic int getItemViewType(int position) {return getData(getRealPosition(position)).getType();}@Overridepublic 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.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);}@Overridepublic 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);}@Overridepublic 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);}@Overrideprotected 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);}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();start();}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();stop();}class BannerOnPageChangeCallback extends ViewPager2.OnPageChangeCallback {private int mTempPosition = INVALID_VALUE;private boolean isScrolled;@Overridepublic 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);}}@Overridepublic 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);}}}@Overridepublic 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);}@Overridepublic 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() {@Overridepublic 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;}@Overridepublic void onStart(LifecycleOwner owner) {start();}@Overridepublic void onStop(LifecycleOwner owner) {stop();}@Overridepublic 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 {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Overridepublic 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();}}}@Overridepublic void onPageScrollStateChanged(int state) {}}//当前position(显示出来的position,非真实positionprivate int mPosition = 0;private Handler mHandler = new Handler();//轮播切换的runnableprivate Runnable runnable = new Runnable() {@Overridepublic 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(){@Overridepublic 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(){@Overridepublic 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;}@Overridepublic void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {@Overrideprotected 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;}
}
Android——支持图片加视频无限轮播的banner相关推荐
- android 轮换实现动画,怎么在Android中实现一个首页无限轮播功能
怎么在Android中实现一个首页无限轮播功能 发布时间:2021-04-12 16:22:49 来源:亿速云 阅读:74 作者:Leah 这期内容当中小编将会给大家带来有关怎么在Android中实现 ...
- 使用element走马灯 + video-player实现图片和视频混合轮播
准备工作: 安装element-ui npm i element-ui -s 安装 video-player npm i vue-video-player -s 引入video-player impo ...
- Android自定义View实现自动无限轮播,点击图片进入详情页
图片接口 http://120.27.23.105/ad/getAd 需要导入Glide设置图片的依赖 compile 'com.github.bumptech.glide:glide:3.7.0' ...
- android 3d布局轮播,android 图片/视频混合轮播控件banner
android 图片/视频混合轮播控件banner 在youth5201314的图片轮播控件做的修改 原作者github地址:https://github.com/youth5201314/banne ...
- BannerLayoutSimple 支持图片无限轮播的 BannerLayout,支持自定义小圆点位置以及显示位置等等,反正支持的挺多的,源码也有注释
BannerLayoutSimple 项目地址: 7449/BannerLayoutSimple 简介:支持图片无限轮播的 BannerLayout,支持自定义小圆点位置以及显示位置等等,反正支持的挺 ...
- Android的轮播图Banner之本地加载和网络加载图片(含demo)
前言 关于轮播图 我个人是比较喜欢 闲来无事的时候 可以整理自己的照片 做一个demo 看看动态的效果 挺不错的! 每个App也基本都有一些广告位置 只要打开这个页面就会无限轮播广告图片 看多了我们也 ...
- Android实现广告页图片无限轮播
一.概述 对于一个联网的Android应用, 首页广告无限轮播基本已经成为标配了. 那么它是怎么实现的呢? 有几种实现方式呢? 二.无限轮播的实现 1.最常规的手段是用 ViewPager来实现 2. ...
- android 手机左右移动图片轮播,js实现支持手机滑动切换的轮播图片效果实例
本文实例讲述了js实现支持手机滑动切换的轮播图片效果的方法.分享给大家供大家参考.具体如下: 运行效果如下: 完整实例代码点击此处本站下载. 使用方法案例: $('.carousel-image'). ...
- 制作支持View,图片轮播的Banner
之前有一个项目中有用到轮播,不过不是简单的轮播图片就完了,而是要轮播很多个View,一开始我的想法和大家一样在github在一个算了,哈哈,不过在试用了很多个项目之后都觉得不能完全满足我的需求,大部分 ...
- iOS开发之ImageView复用实现图片无限轮播
在上篇博客中iOS开发之多图片无缝滚动组件封装与使用给出了图片无限轮播的实现方案之一,下面在给出另一种解决方案.今天博客中要说的就是在ScrollView上贴两个ImageView, 把ImageVi ...
最新文章
- Agiliq署名的免费python书籍
- 获取web.py上面的示例code
- 通过nginx在window下部署项目
- devops 开源_DevOps扩大您的开源凭证
- 向shell脚本中传入参数
- java java se_Java SE 9:尝试资源改进
- 【Leetcode_easy】1103. Distribute Candies to People
- 数值计算详细笔记(一):基础数学知识回顾
- 追求神乎其技的程式设计之道
- 计算机桌面美化软件,电脑桌面软件哪个好 桌面美化管理软件推荐
- java ZipEntry 压缩 解压缩 在linux下中文乱码问题解决
- windows聚焦失效的解决办法
- mp4文件如何转换为webm格式 1
- 怎么读取cf卡id_simotion读写CF卡,保存/读取变量
- Shopnc之nginx安装
- 华为2019算法大赛CTR预估数据探索
- springboot项目启动报错 url‘ attribute is not specified and no embedded datasource could be configured
- 同或门真值表_逻辑函数表达式、真值表.ppt
- C#中Array类精讲
- GoF设计模式——状态模式(C++实现)
热门文章
- nginx代理邮件服务
- linux中如何升级驱动程序,Redhat 如何升级显卡驱动?
- android 手机内存分配,【扫盲贴』关于android手机中RAM(也就是 运行内存)的分配...
- 无损数据压缩算法c语言,C语言实现无损压缩算法
- 如果在遨游浏览器里设置Bing(必应)搜索为默认搜索
- 网站打开缓慢的原因有哪些?
- 文献阅读(03)Computing Graph Neural Networks: A Survey from Algorithms to Accelerators
- 【论文笔记】 Leverage Lexical Knowledge Base for Chinese NER via Collborative Graph Network
- 产品思维训练 | 常见的用户增长手段有哪些?
- 安卓 魔窗SDK 快速接入