微信扫一扫

028-83195727 , 15928970361
business@forhy.com

实现类似天猫列表消息自动垂直滚动效果

android,滚动列表,天猫,自定义控件2016-05-31

一、先看效果

前段时间在交流群里看到有小伙伴在问一个消息滚动列表怎么做,正好最近在学校准备毕业答辩,公司请了两周假,忙里偷闲把这个效果实现了,整体感觉还是不错的,代码量也比较少,练练手的同时也给小伙伴们分享一下。先上图:

二、实现原理

就这个效果第一眼看到的时候有点想用自定View来写,感觉有点像歌词翻动的效果,不过思考最后还是没有用这个方案,主要是实现起来有点麻烦,而且这个效果用自定义ViewGroup来做更为方便,逻辑也更清晰。
整个控件为一个继承自FramLayout的ViewGroup,内部放置两个子控件,用于视图的切换,为了响应外部点击事件,我用了两个Button作为子控件,当然也可以用TextView。在初始化时布局内部两个子控件的位置,第一个子控件位于第二个子控件的上方,
 getChildAt(0).layout(0,0,mWidth,mHeight);
 getChildAt(1).layout(0,mHeight,mWidth,mHeight*2);
而我们的整个控件大小与子控件大小一致。之后滚动整个控件,这时也就形成了第一个控件往上走而第二个控件在后面跟着的效果,在下方子控件完全显示在界面中间时我们要做的处理是重新布局整个页面,将上部不可见的子控件调到显示控件的下方,并在这个时候改变不可见控件的内容,用OnPreTextChangeListener来供外部改变子控件的内容,需要注意的是,子控件默认都应该是TextView或者其子类型。以此实现切换效果,这一步我是这么做的:
//改变设置布局参数  为了显示停顿效果   这里延迟了3秒滚动
 ischange = !ischange;
 postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScroller.startScroll(0, 0, 0, mHeight, 2000);
                    scrollTo(0, 0);
                    requestLayout();

                    if(getChildAt(0).getBottom() == mHeight){
                        mlistener.SetOnPreTextChangeListener((TextView) getChildAt(0));
                    }else if(getChildAt(1).getBottom() == mHeight){
                        mlistener.SetOnPreTextChangeListener((TextView) getChildAt(1));
                    }
                    postInvalidate();
                }
            },3000);

     @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
//        Log.e("TAG","onLayout");
        //来回切换内部两个控件的位置   形成来回滑动效果
            if(ischange){
                getChildAt(1).layout(0,0,mWidth,mHeight);
                getChildAt(0).layout(0,mHeight,mWidth,mHeight*2);
            }else{
                getChildAt(0).layout(0,0,mWidth,mHeight);
                getChildAt(1).layout(0,mHeight,mWidth,mHeight*2);
            }
    }

最后,在界面从window移除是做点扫尾工作:

 @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mScroller.abortAnimation();
        getHandler().removeCallbacksAndMessages(null);
    }

三、具体实现

 /** 改变布局*/
    private boolean ischange = false;
    /** */
    private Scroller mScroller;
    /** 高度*/
    private int mHeight ;
    /** 宽度*/
    private int mWidth ;
    /** 文字改变前的监听   在这里给控件设置内容*/
    private OnPreTextChangeListener mlistener;
    public void setOnPreTextChangeListener(OnPreTextChangeListener mlistener){
        this.mlistener = mlistener;
    }
    public RollView(Context context) {
        this(context, null);
    }

    public RollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RollView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public RollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mScroller = new Scroller(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取控件宽高
        mHeight = getChildAt(0).getMeasuredHeight();
         mWidth = getChildAt(0).getMeasuredWidth();
    }

    @Override
    public void computeScroll() {
        //判断scroller是否滚动结束
        if (mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }else if (getScrollY() == mHeight){
            //改变设置布局参数
            ischange = !ischange;

            postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScroller.startScroll(0, 0, 0, mHeight, 2000);
                    scrollTo(0, 0);
                    requestLayout();

                    if(getChildAt(0).getBottom() == mHeight){
                        mlistener.SetOnPreTextChangeListener((TextView) getChildAt(0));
                    }else if(getChildAt(1).getBottom() == mHeight){
                        mlistener.SetOnPreTextChangeListener((TextView) getChildAt(1));
                    }
                    postInvalidate();
                }
            },3000);
        }else if(getScrollY() == 0){
            //第一次进来
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScroller.startScroll(0, 0, 0, mHeight,2000);
                    postInvalidate();
                }
            },3000);
        }
    }

    public interface OnPreTextChangeListener{
        void SetOnPreTextChangeListener(TextView tv);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if(getChildCount() != 2){
            new IllegalArgumentException("must be has 2 children.");
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mScroller.abortAnimation();
        getHandler().removeCallbacksAndMessages(null);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
//        Log.e("TAG","onLayout");
        //来回切换内部两个控件的位置   形成来回滑动效果
            if(ischange){
                getChildAt(1).layout(0,0,mWidth,mHeight);
                getChildAt(0).layout(0,mHeight,mWidth,mHeight*2);
            }else{
                getChildAt(0).layout(0,0,mWidth,mHeight);
                getChildAt(1).layout(0,mHeight,mWidth,mHeight*2);
            }
    }

mainactivity:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private int num = 0;

    private String[] mStrData = {"苏宁看不下去,全场免费送!","58同城来凑热闹!","京东大促,全场5元!","天猫不服,全场不要钱!"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RollView rv = (RollView) findViewById(R.id.rollview);
        Button mbtn_jingdong = (Button) findViewById(R.id.jingdong);
        Button mbtn_tianmao = (Button) findViewById(R.id.tianmao);

        mbtn_jingdong.setOnClickListener(this);
        mbtn_tianmao.setOnClickListener(this);
        rv.setOnPreTextChangeListener(new RollView.OnPreTextChangeListener() {
            @Override
            public void SetOnPreTextChangeListener(TextView tv) {
               if(num >= mStrData.length){
                   num = 0;
               }
                tv.setText(mStrData[num]);
                num++;
            }
        });
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.jingdong:
            case R.id.tianmao:
                Toast.makeText(this,((TextView)v).getText().toString().trim(),Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

    <path.example.com.mycustom.RollView
        android:id="@+id/rollview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        >

        <Button
            android:id="@+id/jingdong"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="京东大促,全场5元!"
            android:textColor="#f60"
            android:drawableLeft="@mipmap/tm_rate_icon_star_full"
            android:background="#e2e2e2"
            android:textSize="20sp"/>
        <Button
            android:id="@+id/tianmao"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="天猫不服,全场不要钱!"
            android:textColor="#f60"
            android:drawableLeft="@mipmap/tm_rate_icon_star_full"
            android:background="#e2e2e2"
            android:textSize="20sp"/>
    </path.example.com.mycustom.RollView>
    </RelativeLayout>

整体实现相对不是很复杂,主要有几点:动态的给子控件布局和赋值,在整体实现过程中用到了Scroller这个辅助类,下面就简单讲下这个辅助类的使用。

四、Scroller

Scroller本身不能让控件滚动,要让控件滚动要用到View的computeScroll方法,在computeScroll方法中进行判断是否滚动结束,对控件做相应的处理。Scroller的几个主要方法:

//开始滚动  startX startY 开始的X,Y值  dx  dy 水平和垂直位置滚动的距离  duration 滚动所用的时间  默认为250毫秒
 public void startScroll(int startX, int startY, int dx, int dy, int duration) ;
 //是否滚动到指定位置
 public boolean computeScrollOffset();
 //是否滚动完成
 public final boolean isFinished() ;
 //中断动画 cause the scroller to move to the final x and y position
  public void abortAnimation() ;