自实现仿淘宝头条消息滚动的ViewFlipper
最近需要实现一个需求,在列表中,每个item支持显示仿淘宝的上下消息滚动的功能,最开始的考虑是直接使用android自带的ViewFlipper,添加自定的动画来完成,在测试的时候,发现如果每个item的消息太长的时候,列表滑动起来会非常卡,经过分析应该是每个item的消息列表加入到ViewFlipper的时候,都会创建一个新的View,并加入到布局中,从而在滑动的时候,该操作执行太频繁,导致卡顿。因此考虑自己实现一个ViewFlipper,最多通过两个View之间轮换即可,同时对View进行复用,不需要创建那么多的View。
效果图
github:https://github.com/newhope1106/ViewFlipper
一、分析问题关键点
1.每个item的消息上下滚动的效果?其本质是两个控件通过动画上下滚动,来实现轮播,最少需要两个控件,考虑到性能问题,可以对控件进行复用。
2.消息控件的布局未知,动画持续时间和消息滚动间隔未知?可以通过适配器提供消息控件,并且在适配器中用户自己设置消息值,而自实现的ViewFlipper只需要考虑自己的职责,也就是消息滚动效果即可。
二、实现
1.控件实现代码
public class TextViewFlipper extends FrameLayout { /** * 只保留2个控件,进行动画轮播 * */ private View mView1; private View mView2; private int mIndex = 0; private boolean mStarted; private boolean mRunning; private ValueAnimator mAnimator; private BaseFlipperAdapter mAdapter; private int mFlipInterval = BaseFlipperAdapter.DEFAULT_FLIP_INTERVAL; private int mAnimDuration = BaseFlipperAdapter.DEFAULT_DURATION; private final Runnable mFlipRunnable = new Runnable() { @Override public void run() { if (mRunning) { showNext(); postDelayed(mFlipRunnable, mFlipInterval); } } }; public TextViewFlipper(Context context) { this(context, null); } public TextViewFlipper(Context context, AttributeSet attrs) { super(context, attrs); } /** * 设置适配器,并且开始动画 * */ public void setFlipperAdapter(BaseFlipperAdapter adapter){ mAdapter = adapter; if(adapter != null){ initData(); } else { stopFlipping(); } } /** * 获取适配器 * */ public BaseFlipperAdapter getAdapter() { return mAdapter; } private void initData(){ mFlipInterval = mAdapter.getFlipInterval(); mAnimDuration = mAdapter.getAnimDuration(); int count = mAdapter.getCount(); if(count >= 1){ mView1 = mAdapter.getView(mView1, 0); } if(count > 1){ mView2 = mAdapter.getView(mView2, 1); } reset(); if(count >= 1 && mView1 != null){ mView1.setVisibility(VISIBLE); } } /** * 重置所有状态 * */ private void reset(){ mStarted = false; mRunning = false; mIndex = 0; removeCallbacks(mFlipRunnable); if(mAnimator != null){ mAnimator.cancel(); } removeAllViews(); if(mView1 != null){ mView1.setVisibility(GONE); mView1.setTranslationY(0); mView1.setAlpha(1.0f); addView(mView1); } if(mView2 != null){ mView2.setVisibility(GONE); mView2.setTranslationY(0); mView2.setAlpha(1.0f); addView(mView2); } } /** * 开始轮播 * */ public void startFlipping() { if(mAdapter==null || mAdapter.getCount() == 0){ return; } if(mAdapter.getCount() == 1 && !mAdapter.startWhenOnlyOne()){ return; } mStarted = true; updateRunning(); } /** * 结束轮播 * */ public void stopFlipping() { mStarted = false; updateRunning(); } private void updateRunning() { boolean running = mStarted; if (running != mRunning) { if (running) { showOnly(); postDelayed(mFlipRunnable, mFlipInterval); } else { removeCallbacks(mFlipRunnable); if(mAnimator != null){ mAnimator.cancel(); } } mRunning = running; } } /** * 开始动画 * */ private void showOnly() { if(mAnimator == null){ mAnimator = ValueAnimator.ofFloat(0f, 1.0f); mAnimator.setDuration(mAnimDuration); mAnimator.setRepeatCount(0); mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { float value = (float)valueAnimator.getAnimatedValue(); int count = getChildCount(); int childIndex = mIndex % 2; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (i == childIndex) { child.setAlpha(value); child.setTranslationY(getHeight()*(1-value)); child.setVisibility(View.VISIBLE); } else { if(child.getVisibility() == VISIBLE){ child.setAlpha(1 - value); child.setTranslationY(- getHeight()*value); } } } } }); } mAnimator.start(); } public void showNext() { setDisplayedChild(mIndex + 1); } /** * 设置特定View的值 * */ private void setDisplayedChild(int whichChild) { mIndex = whichChild; if (whichChild >= mAdapter.getCount()) { mIndex = 0; } else if (whichChild < 0) { mIndex = mAdapter.getCount() - 1; } if(mIndex % 2 == 0){ View tempView = mAdapter.getView(mView1, mIndex); //如果属于重新创建的控件,则需要重新添加 if(tempView.getParent() != this){ removeView(mView1); addView(tempView, 0); } mView1 = tempView; } else { View tempView = mAdapter.getView(mView2, mIndex); //如果属于重新创建的控件,则需要重新添加 if(tempView.getParent() != this){ removeView(mView2); addView(tempView, 1); } mView2 = tempView; } boolean hasFocus = getFocusedChild() != null; // This will clear old focus if we had it showOnly(); if (hasFocus) { // Try to retake focus if we had it requestFocus(FOCUS_FORWARD); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); reset(); } }
以上通过两个View来实现对View进行复用,而无需重新创建,也无需创建多个View
2.适配器实现代码
public abstract class BaseFlipperAdapter { /** * 两次item切换的间隔 * */ public static final int DEFAULT_FLIP_INTERVAL = 3000; /** * 获取动画持续时间 * */ public static final int DEFAULT_DURATION = 500; /** * 获取数据个数 * @return 返回数据个数 * */ public abstract int getCount(); /** * 获取当前要显示的控件 * */ public abstract View getView(View convertView, int position); /** * 获取item切换的间隔 * */ public int getFlipInterval(){ return DEFAULT_FLIP_INTERVAL; } /** * 获取动画持续时间 * */ public int getAnimDuration(){ return DEFAULT_DURATION; } /** * 只有一个item的时候,是否开启动画,默认false * */ public boolean startWhenOnlyOne(){ return false; } }
以上适配器提供需要的接口数据
3.使用
public class DemoActivity extends Activity{ /**模拟数据*/ private List<List<String>> mockDatas = new ArrayList<>(); @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); initMockData(); setContentView(R.layout.activity_demo); ListView listView = findViewById(R.id.list_view); listView.setAdapter(new ListAdapter()); } /** * 生成模拟数据 * */ private void initMockData(){ for(int i=0; i<100; i++){ List<String> data = new ArrayList<>(); data.add(i + "家人给2岁孩子喝这个,孩子智力倒退10岁!!!"); data.add(i + "iPhone8最感人变化成真,必须买买买买!!!!"); data.add(i + "简直是白菜价!日本玩家33万甩卖15万张游戏王卡"); mockDatas.add(data); } } /** * 实现适配器 * */ private class FlipperAdapter extends BaseFlipperAdapter{ private List<String> mData; public void setData(List<String> data){ mData = data; } @Override public int getCount() { return mData == null ? 0 : mData.size(); } @Override public View getView(View convertView, int position) { if(convertView == null){ convertView = View.inflate(DemoActivity.this, R.layout.layout_flipper_item, null); } TextView textView = (TextView) convertView; textView.setText(mData.get(position)); return convertView; } } private class ListAdapter extends BaseAdapter { @Override public int getCount() { return mockDatas.size(); } @Override public Object getItem(int i) { return mockDatas.get(i); } @Override public long getItemId(int i) { return i; } @Override public View getView(int position, View convertView, ViewGroup viewGroup) { if(convertView == null){ convertView = getLayoutInflater().inflate(R.layout.list_view_item, null); } TextViewFlipper textViewFlipper = (TextViewFlipper)convertView.findViewById(R.id.text_view_flipper); FlipperAdapter adapter = (FlipperAdapter)textViewFlipper.getAdapter(); if(adapter == null){ adapter = new FlipperAdapter(); } adapter.setData(mockDatas.get(position)); textViewFlipper.setFlipperAdapter(adapter); textViewFlipper.startFlipping(); return convertView; } } }
以上demo是在listview中实现上下滚动功能,如果只是单个控件实现上下滚动功能也是支持的。
三、改进点
1. 以上没有把动画部分抽取出来,给用户自定义,依赖了特定的业务,并不完善。
2. 使用方面步骤有点多,成本略高,可以再加一层封装,来实现一些领域的业务,对外提供简单的接口

Android仿淘宝头条垂直滚动广告条效果
2017年02月07日 27.88MB 下载
RecyclerViewHeader+ViewFlipper仿淘宝头条滚动效果
1、需要导入的包: //RecyclerView compile 'com.android.support:recyclerview-v7:23.1.0' //RecyclerV...
Android【垂直滚动广告条】仿淘宝头条1号店京东—垂直滚动广告条
淘宝头条是淘宝App中很经典的一个功能显示,主要用于显示最近的热评新闻,显示主要方式为文字竖直滚动效果,下面简单阐述一下本demo所涉及到的技术点以及功能展示 1.主要用到的控件为Andro...
Android中仿淘宝头条,自定义控件,向上滚动
1效果图 2项目结构 3代码 自定义滑动控件UpDownTextView package android.zhh.com.myfangtaobao2; import android.a...
ViewFlipper的使用,仿淘宝头条垂直滚动广告条
2017年01月17日 1.98MB 下载
自定义控件实现(淘宝头条/京东快报)垂直循环滚动栏目
1、通过继承LinearLayout的方式 ①自定义属性 xml version="1.0" encoding="utf-8"?> name="JDAdverView"> ...
仿淘宝头条,走马灯上下滚动
2016年01月18日 1.99MB 下载
Android仿淘宝头条滚动广告条 ViewFlipper
2018年03月15日 15.51MB 下载
Android仿淘宝滚动的头条
http://www.jianshu.com/p/73c04d93cc56 1. 先看效果图 marquee_view.gif 感谢鸿洋大神,我是从他的博客上看到...
android 实现淘宝消息滚动条
因为最近有项目需求,需要实现一个类似淘宝的消息滚动条,如下图所示。 比如上面图上的这个糯米头条功能,通过Ui automator view 发现糯米是通过一个listview的方式实现的,但是...
仿淘宝头条
2016年06月27日 81KB 下载
热门文章
- google的GCM推送使用简介
阅读量:15765
- android四大组件启动流程-BroadcastReceiver启动流程(基于android 6.0)
阅读量:1798
- Activity启动流程,界面绘制到事件处理的整个流程(基于Android6.0源码)(2)
阅读量:1296
- 上拉加载更多,下拉刷新的弹性ListView的实现
阅读量:1295
- OkHttp 3.x框架简要分析
阅读量:920
最新评论
- google的GCM推送使用简介
newhope1106:[reply]u011429034[/reply] GCM是系统里面集成的推送服务,不是我们一般用...
- google的GCM推送使用简介
gaga_king:[reply]u011429034[/reply] 这个数系统级的推送,像小米推送在miui系统上...
- google的GCM推送使用简介
u011429034:你确信,如果程序的进程被kill掉了,还能在接收到推送的么?
- android四大组件启动流程 -...
newhope1106:以上ActivityManagerService.publishService会调用到Activi...
- Activity启动流程,界面绘制...
newhope1106:以上需要注意一定,子View可以通过调用父View的requestDisallowIntercep...
0
收藏
评论