项目需求 要做一个 上下 播放通知的 控件 于是。。。
/**
* 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);
}
}
好了。。。。