仿淘宝、京东滚动播放控件

    技术2024-06-14  80

    项目需求 要做一个 上下 播放通知的 控件 于是。。。

    /** * ViewFlipper 仿淘宝、京东滚动播放控件 * */ public class ViewsFlipper extends FrameLayout { @SuppressWarnings("unused") private static final String TAG = ViewsFlipper.class.getSimpleName(); private static final int DEFAULT_FLIP_DURATION = 500; private static final int DEFAULT_FLIP_INTERVAL = 3000; /** * animations interval */ private long mFlipInterval = DEFAULT_FLIP_INTERVAL; /** * animations duration */ private long mFlipDuration = DEFAULT_FLIP_DURATION; /** * 动画偏移量,涉及到View动画滚动距离,当onSizeChanged时候获取 * (为什么不从onMeasure?是因为每次设置childView VISIBLE的时候都会触发重绘,每次都要执行onMeasure,感觉太频繁了) */ private int mTranslationY; private int mTranslationX; /** * view in animator */ private ObjectAnimator mInAnimator; /** * view out animator */ private ObjectAnimator mOutAnimator; /** * is the view visible or not. * default is false, changed when view visibility change. or * {@link ViewsFlipper#onAttachedToWindow()} * {@link ViewsFlipper#onDetachedFromWindow()} */ private boolean mVisible = false; /** * is view begin start flipping or not * when call {@link ViewsFlipper#startFlipping()} set mStart true. * when call {@link ViewsFlipper#stopFlipping()} ()} set mStart false. */ private boolean mStarted = false; /** * if flipper is running or not * determined by mVisible && mStarted */ private boolean mRunning = false; /** * current show view index */ private int mWhichChild = 0; /** * current data index, get from {@link RecyclerView.Adapter#getItemCount()) */ private int mPosition = 0; /** * view scroll orientation */ @RecyclerView.Orientation private int mOrientation = RecyclerView.VERTICAL; private RecyclerView.Adapter<RecyclerView.ViewHolder> mAdapter; /** * shadowed child view */ private RecyclerView.ViewHolder shadowedVH; /** * showing child view */ private RecyclerView.ViewHolder showingVH; private AnimatorSet animatorSet; private RecyclerView.AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { super.onChanged(); // when data changed, reset view. reset(); // if animator set is not null, maybe the view is in animation, // so we need end the animator first. if (animatorSet != null) { animatorSet.end(); } if (mAdapter == null || mAdapter.getItemCount() <= 0) { throw new IllegalArgumentException("please call ViewsFlipper.setAdapter first and set non-empty data!"); } // restart from first child view. mRunning = true; //show first child view immediately. mAdapter.bindViewHolder(showingVH, 0); setDisplayedChild(0, false); //cycling show next view. postDelayed(mFlipRunnable, mFlipInterval); } @Override public void onItemRangeChanged(int positionStart, int itemCount) { super.onItemRangeChanged(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { super.onItemRangeChanged(positionStart, itemCount, payload); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { super.onItemRangeInserted(positionStart, itemCount); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { super.onItemRangeRemoved(positionStart, itemCount); } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { super.onItemRangeMoved(fromPosition, toPosition, itemCount); } }; public ViewsFlipper(Context context) { this(context, null); } public ViewsFlipper(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } / public methods /// / below /** * Start a timer to cycle through child views * without show animation. */ public void startFlipping() { if (mAdapter == null) { throw new IllegalArgumentException("you must call ViewsFlipper.setAdapter first!"); } mStarted = true; updateRunning(false); } /** * stop flip */ public void stopFlipping() { mStarted = false; updateRunning(); } /** * How long to wait before flipping to the next view * * @param milliseconds times in milliseconds */ public void setFlipInterval(long milliseconds) { this.mFlipInterval = milliseconds; if (mFlipInterval < mFlipDuration) { throw new IllegalArgumentException("flip interval must set bigger than flip duration!!!"); } } @SuppressWarnings("unused") public long getFlipInterval() { return mFlipInterval; } /** * set how long flipping in/out animation cost * * @param milliseconds times in milliseconds */ public void setFlipDuration(long milliseconds) { mFlipDuration = milliseconds; if (mFlipInterval < mFlipDuration) { throw new IllegalArgumentException("flip interval must set bigger than flip duration!!!"); } mInAnimator.setDuration(milliseconds); mOutAnimator.setDuration(milliseconds); } @SuppressWarnings("unused") public long getFlipDuration() { return mFlipDuration; } /** * set the scroll orientation * * @param orientation {@link RecyclerView.Orientation} */ public void setOrientation(@RecyclerView.Orientation int orientation) { mOrientation = orientation; boolean vertical = mOrientation == RecyclerView.VERTICAL; if (animatorSet != null) { // if animator set is not null, maybe the view is in animation, // so we need end the animator first. animatorSet.end(); } // reset the shadowed view position, // because Animator change the View position forever, and we have move the shadowed view out of screen // so we need to reset the shadowed view to right position. if (shadowedVH != null) { if (vertical) { shadowedVH.itemView.setX(showingVH.itemView.getX()); } else { shadowedVH.itemView.setY(showingVH.itemView.getY()); } } if (mInAnimator != null) { mInAnimator.setPropertyName(vertical ? "translationY" : "translationX"); mInAnimator.setFloatValues(vertical ? mTranslationY : mTranslationX, 0); } if (mOutAnimator != null) { mOutAnimator.setPropertyName(vertical ? "translationY" : "translationX"); mOutAnimator.setFloatValues(0, vertical ? -mTranslationY : -mTranslationX); } } @SuppressWarnings("unused") public int getOrientation() { return mOrientation; } @SuppressWarnings("unchecked cast, unused") public <VH extends RecyclerView.ViewHolder, T extends RecyclerView.Adapter<VH>> void setAdapter(T adapter) { if (adapter == null || adapter.getItemCount() <= 0) { throw new IllegalArgumentException("please call ViewsFlipper.setAdapter first and set non-empty data!"); } reset(); this.removeAllViews(); this.mAdapter = (RecyclerView.Adapter<RecyclerView.ViewHolder>) adapter; this.mAdapter.registerAdapterDataObserver(mObserver); showingVH = mAdapter.createViewHolder(this, 0); shadowedVH = mAdapter.createViewHolder(this, 0); //noinspection ConstantConditions if (showingVH == null || shadowedVH == null) { throw new IllegalArgumentException("ViewHolder must be not null!"); } //add child view to parent addView(showingVH.itemView); addView(shadowedVH.itemView); showingVH.itemView.setVisibility(View.VISIBLE); shadowedVH.itemView.setVisibility(View.INVISIBLE); mAdapter.bindViewHolder(showingVH, 0); } private void reset() { removeCallbacks(mFlipRunnable); mPosition = 0; mWhichChild = 0; mRunning = false; } /** * init flipper animation and setting */ private void init(Context context, AttributeSet attrs) { initAnimation(); if (null != attrs) { /* get config from xml files */ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewsFlipper); setFlipDuration(a.getInteger(R.styleable.ViewsFlipper_flipDuration, DEFAULT_FLIP_DURATION)); setFlipInterval(a.getInteger(R.styleable.ViewsFlipper_flipInterval, DEFAULT_FLIP_INTERVAL)); a.recycle(); } } private void initAnimation() { mInAnimator = defaultInAnimator(); mOutAnimator = defaultOutAnimator(); } private ObjectAnimator defaultInAnimator() { ObjectAnimator animY = ObjectAnimator.ofFloat(null, "translationY", mTranslationY, 0); ObjectAnimator animX = ObjectAnimator.ofFloat(null, "translationX", mTranslationX, 0); ObjectAnimator anim; if (mOrientation == RecyclerView.VERTICAL) { anim = animY; } else { anim = animX; } anim.setDuration(DEFAULT_FLIP_DURATION); return anim; } private ObjectAnimator defaultOutAnimator() { ObjectAnimator animY = ObjectAnimator.ofFloat(null, "translationY", 0, -mTranslationY); ObjectAnimator animX = ObjectAnimator.ofFloat(null, "translationX", 0, -mTranslationX); ObjectAnimator anim; if (mOrientation == RecyclerView.VERTICAL) { anim = animY; } else { anim = animX; } anim.setDuration(DEFAULT_FLIP_DURATION); return anim; } @Override protected void onSizeChanged(int w, int h, int oldW, int oldH) { super.onSizeChanged(w, h, oldW, oldH); mTranslationY = getMeasuredHeight(); mTranslationX = getMeasuredWidth(); setOrientation(mOrientation); } /** * update view */ private void updateRunning() { updateRunning(true); } /** * only show current child index view. set other view invisible. * * @param childIndex child view index * @param animate is showing with animation in */ void showOnly(int childIndex, boolean animate) { final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); final View preChild = getChildAt((i == 0) ? count - 1 : i - 1); if (i == childIndex) { if (animate && mInAnimator != null) { mOutAnimator.setTarget(preChild); mInAnimator.setTarget(child); animatorSet = new AnimatorSet(); animatorSet.playTogether(mOutAnimator, mInAnimator); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); child.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); preChild.setVisibility(View.INVISIBLE); } }); animatorSet.start(); } else { // if not set animation, or animate is false, // then show child view immediately. child.setVisibility(View.VISIBLE); } } } } /** * begin running * * @param flipNow animation or not */ private void updateRunning(boolean flipNow) { boolean running = mVisible && mStarted; if (running != mRunning) { if (running) { showOnly(mWhichChild, flipNow); postDelayed(mFlipRunnable, mFlipInterval); } else { removeCallbacks(mFlipRunnable); } mRunning = running; } } /** * show next view */ protected void showNext() { // if the flipper is currently flipping automatically, and showNext() is called // we should we should make sure to reset the timer if (mRunning) { removeCallbacks(mFlipRunnable); postDelayed(mFlipRunnable, mFlipInterval); } //add child index and data index. cycling show. mPosition = mPosition >= mAdapter.getItemCount() - 1 ? 0 : mPosition + 1; mWhichChild = ((mWhichChild >= getChildCount() - 1) ? 0 : mWhichChild + 1); setDisplayedChild(mWhichChild); } /** * set display view by index in parent * * @param whichChild the display view index */ private void setDisplayedChild(int whichChild) { setDisplayedChild(whichChild, true); } private void setDisplayedChild(int whichChild, boolean animate) { //swap shadowed view and showing view. if (showingVH.itemView.getVisibility() == View.VISIBLE) { swapViewHolder(); } mAdapter.bindViewHolder(showingVH, mPosition); boolean hasFocus = getFocusedChild() != null; showOnly(whichChild, animate); if (hasFocus) { // Try to retake focus if we had it requestFocus(FOCUS_FORWARD); } } private void swapViewHolder() { RecyclerView.ViewHolder tmp = showingVH; showingVH = shadowedVH; shadowedVH = tmp; } private final Runnable mFlipRunnable = new Runnable() { @Override public void run() { if (mRunning) { ViewsFlipper.this.showNext(); } } }; @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mVisible = true; startFlipping(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mVisible = false; stopFlipping(); } @Override protected void onWindowVisibilityChanged(int visibility) { super.onWindowVisibilityChanged(visibility); mVisible = (visibility == VISIBLE); updateRunning(false); } } <!-- ViewFlipper 仿淘宝、京东滚动播放控件--> <declare-styleable name="ViewsFlipper"> <attr name="flipInterval" format="integer"/> <attr name="flipDuration" format="integer"/> </declare-styleable>

    使用

    <ViewsFlipper android:id="@+id/share_flipper" android:layout_width="match_parent" android:layout_height="wrap_content" app:flipDuration="500" app:flipInterval="2500" />

    使用跟 RecyclerView 一样 图片随便复制的连接 勿怪

    //初始化 滚动 视图 private List<Pair<String, String>> items = new ArrayList<>(); private void initFlipper() { items.add(new Pair<>("https://pic2.zhimg.com/v2-8a9c5742806628253e7c0ea80fd76769_xs.jpg", "感受*** 成功邀请好友,获得50金币")); items.add(new Pair<>("https://pic3.zhimg.com/5608a69e052a0f1c1bd72f4785fa3819_xs.jpg", "如何*** 成功邀请好友,获得50金币")); items.add(new Pair<>("https://pic2.zhimg.com/da8e974dc_xs.jpg", "记住*** 成功邀请好友,获得50金币")); items.add(new Pair<>("https://pic2.zhimg.com/v2-8a9c5742806628253e7c0ea80fd76769_xs.jpg", "也许*** 成功邀请好友,获得50金币")); items.add(new Pair<>("https://pic2.zhimg.com/da8e974dc_xs.jpg", "面对***,成功邀请好友,获得50金币")); items.add(new Pair<>("https://pic2.zhimg.com/v2-8a9c5742806628253e7c0ea80fd76769_xs.jpg", "我们*** 成功邀请好友,获得50金币")); items.add(new Pair<>("https://pic3.zhimg.com/5608a69e052a0f1c1bd72f4785fa3819_xs.jpg", "漂浮*** 成功邀请好友,获得50金币")); flipperAdapter=new FlipperAdapter(items); flipper.setAdapter(flipperAdapter); flipper.setOrientation(RecyclerView.VERTICAL); flipper.startFlipping(); } private class FlipperAdapter extends BaseQuickAdapter<Pair<String, String>, BaseViewHolder> { public FlipperAdapter(@Nullable List<Pair<String, String>> data) { super(R.layout.layout_flipper_child,data); } @Override protected void convert(BaseViewHolder helper, Pair<String, String> item) { helper.setText(R.id.tv_child,item.second) .setTextColor(R.id.tv_child,Color.WHITE); CircleImageView icon = helper.getView(R.id.iv_child); new ImageLoaderImpl().loadImage(mContext,item.first).into(icon); } }

    好了。。。。

    Processed: 0.012, SQL: 9