滚动的ViewFlipper——滚动的大标题 自实现仿淘宝头条消息滚动的ViewFlipper

自实现仿淘宝头条消息滚动的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 ViewFlipper 仿淘宝 滚动
个人分类:  控件
海参的功效,北京人不知道海参有这些功效!沈阳盛鼎 · 顶新

Android仿淘宝头条垂直滚动广告条效果

2017年02月07日 27.88MB 下载

“人喝茶三年,茶养人一辈子”已被科学证实!花青堂茶叶 · 顶新

仿淘宝头条,走马灯上下滚动

2016年01月18日 1.99MB 下载

Android仿淘宝头条滚动广告条 ViewFlipper

2018年03月15日 15.51MB 下载

仿淘宝头条

2016年06月27日 81KB 下载

个人资料

原创
28
粉丝
5
喜欢
7
评论
6
等级:
访问:
3万+
积分:
613
排名:
8万+
勋章:

最新评论


猜你喜欢

转载自blog.csdn.net/s2421458535/article/details/80538986