android 消息垂直滚动轮播控件

android 消息垂直滚动轮播控件

本文已更新,请移步最新发布的文章,功能更加完善!ViewsFlipper–最易用的的仿淘宝、京东消息轮播控件
类似淘宝首页的那种消息垂直滚动的控件,实现也很简单,网上也有很多例子,不过网上大多数的例子都是利用android的原生控件ViewFlipper,这个控件我也用了,最后发现很坑爹,有很多问题。
先说使用ViewFliper遇到的问题。首先一个就是ViewFlipper在手机锁屏然后再开屏之后有时候不再自动滚动,看了ViewFlipper的源码就知道,ViewFlipper中注册了一个广播接收器(BroadcastReceiver)用来接收系统锁屏和开屏的消息,并用来控制其中的一个boolean变量mUserPresent,这个变量将直接决定着ViewFlipper是否能自动滚动,但是我测试发现,有时候手机锁屏再开屏之后,ViewFlipper的广播接收器根本没有收到开屏的广播消息,导致变量的状态没有改变,从而导致ViewFlipper无法自动滚动。

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                mUserPresent = false;
                updateRunning();
            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
                mUserPresent = true;
                updateRunning(false);
            }
        }
    };
    ...
    private void updateRunning(boolean flipNow) {
        boolean running = mVisible && mStarted && mUserPresent;
        if (running != mRunning) {
            if (running) {
                showOnly(mWhichChild, flipNow);
                postDelayed(mFlipRunnable, mFlipInterval);
            } else {
                removeCallbacks(mFlipRunnable);
            }
            mRunning = running;
        }
        if (LOGD) {
            Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
        }
    }

而且我在Fragment中使用ViewFlipper还遇到了来回切换之后显示重影的问题,不过这个问题可以在切换的使用调用ViewFlipper的stopFlipping和stopFlippng函数来解决,不过这样做你还是会发现一个问题,就是切回来之后显示的第一个消息是带有入场动画的,但是上面却是空白!
这里写图片描述

查看ViewFlipper的startFlipping源码,可以看到 startFlipping调用了updateRunning(true)方法,这个方法又调用了父类ViewAnimator的showOnly(childId, flipNow)方法,showOnly方法就是根据传入的参数childId展示哪一项子view,并且根据flipNow决定是否播放动画,很不幸,调用startFlipping默认传入的参数就是播放动画,这就导致在显示第一项的时候展示了入场动画,但是上面为什么是空白的,我暂时也没搞懂,那个大神知道了还请告知一下!小菜在此感激不尽。

	public void startFlipping() {
        mStarted = true;
        updateRunning();
    }
    private void updateRunning() {
        updateRunning(true);
    }

既然ViewFlipper不符合要求,那就自己写一个MyViewFlipper吧,借鉴ViewFlipper的源码,改动了部分代码,去掉了广播注册的部分,然后将startFlipping中传入不播放动画,效果如下图:
这里写图片描述
大功告成!
下面贴代码

  • **1、TextViewSwitcher **
package com.example.lee.rollingtextview.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.AnimRes;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ViewAnimator;

import com.example.lee.rollingtextview.R;
import com.example.lee.rollingtextview.RollingTextAdapter;

/**
 * Created by lihuayong on 18/7/20.
 * 代码参考ViewFlipper源码
 * 修改了部分源码,如不再接收手机锁屏和开屏广播,需要调用api手动设置自动播放
 *
 */

public class TextViewSwitcher extends ViewAnimator {

    private static final  String TAG = "TextViewSwitcher";

    private static final int DEFAULT_FLIP_DURATION = 500;

    private static final int DEFAULT_FLIP_INTERVAL = 3000;

    private static final int DEFAULT_IN_ANIMATION = R.anim.rolling_text_in;

    private static final int DEFAULT_OUT_ANIMATION = R.anim.rolling_text_out;

    /**
     * 切换的时间间隔
     * */
    private int mFlipInterval = DEFAULT_FLIP_INTERVAL;

    /**
     * 动画切换时间间隔
     */
    private int mFlipDuration = DEFAULT_FLIP_DURATION;

    @AnimRes
    private int mInAnimation = DEFAULT_IN_ANIMATION;

    @AnimRes
    private int mOutAnimation = DEFAULT_OUT_ANIMATION;

    private boolean mAutoStart = false;
    private boolean mVisible = false;
    private boolean mStarted = false;
    private boolean mRunning = false;

    private RollingTextAdapter mAdapter;

    private OnItemClickListener mListener;

    private int mCurItem = 0;

    public TextViewSwitcher(Context context) {
        this(context,null);
    }

    public TextViewSwitcher(Context context, AttributeSet attrs) {
        super(context, attrs);
        parseConfig(context,attrs);
        init(context);
    }

    private void parseConfig(Context context, @Nullable  AttributeSet attrs){
        if(null != attrs) {
            /** get set from xml files */
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextViewSwitcher);
            mFlipDuration = a.getInteger(R.styleable.TextViewSwitcher_flipDuration, DEFAULT_FLIP_DURATION);
            mFlipInterval = a.getInteger(R.styleable.TextViewSwitcher_flipInterval, DEFAULT_FLIP_INTERVAL);
            mAutoStart = a.getBoolean(R.styleable.TextViewSwitcher_autoStart, false);
            mInAnimation = a.getResourceId(R.styleable.TextViewSwitcher_inAnimation, DEFAULT_IN_ANIMATION);
            mOutAnimation = a.getResourceId(R.styleable.TextViewSwitcher_outAnimation, DEFAULT_OUT_ANIMATION);

            a.recycle();
        }
    }

    private void init(Context context){
        Animation animIn = AnimationUtils.loadAnimation(context, mInAnimation);
        setInAnimation(animIn);
        Animation animOut = AnimationUtils.loadAnimation(context, mOutAnimation);
        setOutAnimation(animOut);

        setFlipInterval(mFlipInterval);
        setFlipDuration(mFlipDuration);
        setAutoStart(mAutoStart);
    }

    /**
     * load subview
     */
    private void start(){
        if(this.mAdapter == null||this.mAdapter.getCount() <= 0){
            return;
        }
        removeAllViews();

        //auto start flipping while data size >= 2
        if(this.mAdapter.getCount() >2)
            setAutoStart(true);
        else
            setAutoStart(false);

        for(int i =0;i<this.mAdapter.getCount();i= i+2){
            View view = this.mAdapter.getView(getContext(),getCurrentView(),i);
            addView(view);
            if(mListener != null){
                view.setTag(i);
                view.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        mListener.onClick((int)view.getTag());
                    }
                });
            }
        }
    }

    /**
     * set view adapter
     * @param adapter the adapter
     */
    public void setAdapter(RollingTextAdapter adapter){
        this.mAdapter = adapter;
        start();
    }

    public void setOnItemClickListener(OnItemClickListener listener){
        this.mListener = listener;
        start();
    }

    /**
     *  更新view显示
     */
    private void updateRunning(){
        updateRunning(true);
    }

    /**
     * 在布局中只显示当前id的子view,其他view不显示
     * @param childIndex 子view的id
     * @param animate 是否要显示动画
     */
    void showOnlyOneChild(int childIndex, boolean animate) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (i == childIndex) {
                if (animate && getInAnimation() != null) {
                    child.startAnimation(getInAnimation());
                }
                child.setVisibility(View.VISIBLE);
            } else {
                if (animate && getOutAnimation() != null && child.getVisibility() == View.VISIBLE) {
                    child.startAnimation(getOutAnimation());
                } else if (child.getAnimation() == getInAnimation())
                    child.clearAnimation();
                child.setVisibility(View.GONE);
            }
        }
    }

    private void updateRunning(boolean flipNow){
        boolean running = mVisible && mStarted ;
        if (running != mRunning) {
            if (running) {
                showOnlyOneChild(getDisplayedChild(),flipNow);
                postDelayed(mRollRunnable, mFlipInterval);
            } else {
                removeCallbacks(mRollRunnable);
            }
            mRunning = running;
        }
    }

    @Override
    public void removeAllViews() {
        super.removeAllViews();
        mCurItem = 0;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        Log.i(TAG,"onAttachedToWindow");

        if(mAutoStart){
            startFlipping();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        Log.i(TAG,"onDetachedFromWindow");

        mVisible = false;
        updateRunning();
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);

        Log.i(TAG,"onWindowVisibilityChanged  "+(visibility == VISIBLE ? "VISIBLE":"INVISIBLE"));
        mVisible = visibility == VISIBLE;
        updateRunning(false);
    }

    /**
     * Set if this view automatically calls {@link #startFlipping()} when it
     * becomes attached to a window.
     */
    public void setAutoStart(boolean autoStart){
        this.mAutoStart = autoStart;
    }

    /**
     * Returns true if this view automatically calls {@link #startFlipping()}
     * when it becomes attached to a window.
     */
    public boolean isAutoStart(){return mAutoStart;}

    /**
     * Start a timer to cycle through child views
     * without show animation.
     */
    public void startFlipping() {
        mStarted = true;
        updateRunning(false);
    }

    /**
     * No more flips
     */
    public void stopFlipping() {
        mStarted = false;
        updateRunning();
    }

    /**
     * Returns true if the child views are flipping.
     */
    public boolean isFlipping() {
        return mStarted;
    }

    /**
     * How long to wait before flipping to the next view
     * @param milliseconds time in milliseconds
     */
    public void setFlipInterval(int milliseconds) {
        this.mFlipInterval = milliseconds;
    }

    /**
     * How long to finish flipping in/out animation
     * @param milliseconds time in milliseconds
     */
    public void setFlipDuration(int milliseconds){
        this.mFlipDuration = milliseconds;
        getInAnimation().setDuration(mFlipDuration);
        getOutAnimation().setDuration(mFlipDuration);
    }

    private final Runnable mRollRunnable = new Runnable() {
        @Override
        public void run() {
            if (mRunning) {
                mCurItem = mCurItem == getChildCount()-1 ? 0 : mCurItem+1;
                showNext();
                postDelayed(mRollRunnable, mFlipInterval);

                Log.i(TAG,"child :" +getDisplayedChild()+" is "+(getChildAt(getDisplayedChild()).getVisibility() == VISIBLE ? "VISIBLE":"INVISIBLE"));
                Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
                        + ", mRunning=" + mRunning);
            }
        }
    };

    public interface OnItemClickListener{
        void onClick(int position);
    }
}
  • 2 RollingTextAdapter
package com.example.lee.rollingtextview;

import android.content.Context;
import android.view.View;

public abstract class RollingTextAdapter {
    public abstract int getCount();
    public abstract View getView(Context context, View contentView, int position);
}

  • 3、RollTextItem
package com.example.lee.rollingtextview.view;

public class RollTextItem {

    public int getImgId() {
        return imgId;
    }

    public void setImgId(int imgId) {
        this.imgId = imgId;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    private int imgId;

    private String msg;

    private int textColor;

    public RollTextItem(String msg, int imgId){
        this(msg,imgId, android.R.color.holo_blue_light);
    }

    public RollTextItem(String msg, int imgId, int textColor){
        this.msg = msg;
        this.imgId = imgId;
        this.textColor = textColor;
    }

    public int getTextColor() {
        return textColor;
    }

    public void setTextColor(int textColor) {
        this.textColor = textColor;
    }
}

  • 4、MainActivity
package com.example.lee.rollingtextview;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.example.lee.rollingtextview.view.RollTextItem;
import com.example.lee.rollingtextview.view.TextViewSwitcher;
import com.example.lee.rollingtextview.view.TextViewSwitcher.OnItemClickListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity {
    private TextViewSwitcher rollingText;
    private Random randomColor;
    private List<RollTextItem> data = new ArrayList<>();
    int[] textColor = {android.R.color.holo_blue_light,android.R.color.holo_green_light,android.R.color.holo_orange_light,
            android.R.color.holo_purple,android.R.color.holo_red_light,android.R.color.black,android.R.color.holo_orange_dark};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        rollingText = findViewById(R.id.rolltext);
        randomColor = new Random();
        final Button btnCtrl = findViewById(R.id.btn_control);
        btnCtrl.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(btnCtrl.getText().equals("停止滚动")){
                    btnCtrl.setText("开始滚动");
                    rollingText.stopFlipping();
                }else{
                    btnCtrl.setText("停止滚动");
                    rollingText.startFlipping();
                }
            }
        });
        initData();
        rollingText.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onClick(int position) {
                Toast.makeText(MainActivity.this,"item 被点击"+position,Toast.LENGTH_SHORT).show();
            }
        });
        rollingText.setAdapter(new RollingTextAdapter() {
            @Override
            public int getCount() {
                return data.size()/2;
            }
            @SuppressLint("ResourceAsColor")
            @Override
            public View getView(Context context, View contentView, int position) {
                View view = View.inflate(context,R.layout.item_layout,null);
                ((TextView)view.findViewById(R.id.tv_1)).setText(data.get(position).getMsg());
                int randomId = randomColor.nextInt(100);
                ((TextView)view.findViewById(R.id.tv_1)).setTextColor(getResources().getColor(textColor[randomId%7]));
                ((TextView)view.findViewById(R.id.tv_2)).setText(data.get((position+1)%data.size()).getMsg());
                randomId = randomColor.nextInt(100);
                ((TextView)view.findViewById(R.id.tv_2)).setTextColor(getResources().getColor(textColor[randomId%7]));
                return view;
            }
        });
    }
    
    private void initData() {
        data.add(new RollTextItem("当你走进这欢乐场",R.drawable.ic_launcher_foreground,textColor[0]));
        data.add(new RollTextItem("背上所有的梦与想",R.drawable.ic_launcher_foreground,textColor[1]));
        data.add(new RollTextItem("各色的脸上各色的妆",R.drawable.ic_launcher_foreground,textColor[2]));
        data.add(new RollTextItem("没人记得你的模样",R.drawable.ic_launcher_foreground,textColor[3]));
        data.add(new RollTextItem("三巡酒过你在角落",R.drawable.ic_launcher_foreground,textColor[4]));
        data.add(new RollTextItem("固执的唱着苦涩的歌",R.drawable.ic_launcher_foreground,textColor[5]));
        data.add(new RollTextItem("听它在喧嚣里被淹没",R.drawable.ic_launcher_foreground,textColor[6]));
        data.add(new RollTextItem("你拿起酒杯对自己说",R.drawable.ic_launcher_foreground,textColor[1]));
        data.add(new RollTextItem("一杯敬朝阳 一杯敬月光",R.drawable.ic_launcher_foreground,textColor[2]));
        data.add(new RollTextItem("唤醒我的向往 温柔了寒窗",R.drawable.ic_launcher_foreground,textColor[3]));
        data.add(new RollTextItem("于是可以不回头地逆风飞翔",R.drawable.ic_launcher_foreground,textColor[4]));
        data.add(new RollTextItem("不怕心头有雨 眼底有霜",R.drawable.ic_launcher_foreground,textColor[5]));
    }
}

  • 4、进场和退场动画roll_text_in和roll_text_out
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="1000"
        android:fromYDelta="100%p"
        android:toYDelta="0"/>
</set>

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="1000"
        android:fromYDelta="0"
        android:toYDelta="-100%p"/>
</set>
  • 5、item_layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <ImageView
            android:id="@+id/img_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher"/>

        <TextView
            android:id="@+id/tv_1"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/holo_purple"
            android:textSize="20sp"
            android:text="hello world"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <ImageView
            android:id="@+id/img_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher"/>

        <TextView
            android:id="@+id/tv_2"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/holo_purple"
            android:textSize="20sp"
            android:text="hello world"/>
    </LinearLayout>

</LinearLayout>

就先这样吧,刚刚开始写博客,XD。其中参考了网上的一些方法,链接没有保存就不在这里一一列举了,还请见谅。
工程demo链接下载地址

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值