Android自定義控件實戰——仿淘寶商品瀏覽界面


 轉載請聲明出處http://blog.csdn.net/zhongkejingwang/article/details/38656929    

用手機淘寶瀏覽商品詳情時,商品圖片是放在后面的,在第一個ScrollView滾動到最底下時會有提示,繼續拖動才能瀏覽圖片。仿照這個效果寫一個出來並不難,只要定義一個Layout管理兩個ScrollView就行了,當第一個ScrollView滑到底部時,再次向上滑動進入第二個ScrollView。效果如下:

需要注意的地方是:

      1、如果是手動滑到底部需要再次按下才能繼續往下滑,自動滾動到底部則不需要

      2、在由上一個ScrollView滑動到下一個ScrollView的過程中多只手指相繼拖動也不會導致布局的劇變,也就是多個pointer的滑動不會導致move距離的劇變。

這個Layout的實現思路是:

     在布局中放置兩個ScrollView,並為其設置OnTouchListener,時刻判斷ScrollView的滾動距離,一旦第一個ScrollView滾動到底部,則標識改為可向上拖動,此時開始記錄滑動距離mMoveLen,根據mMoveLen重新layout兩個ScrollView;同理,監聽第二個ScrollView是否滾動到頂部,以往下拖動。

OK,明白了原理之后可以看代碼了:

  

 
package com.jingchen.tbviewer;  
  
import java.util.Timer;  
import java.util.TimerTask;  
  
import android.content.Context;  
import android.os.Handler;  
import android.os.Message;  
import android.util.AttributeSet;  
import android.view.MotionEvent;  
import android.view.VelocityTracker;  
import android.view.View;  
import android.widget.RelativeLayout;  
import android.widget.ScrollView;  
  
/** 
 * 包含兩個ScrollView的容器 
 *  
 * @author chenjing 
 *  
 */  
public class ScrollViewContainer extends RelativeLayout {  
  
    /** 
     * 自動上滑 
     */  
    public static final int AUTO_UP = 0;  
    /** 
     * 自動下滑 
     */  
    public static final int AUTO_DOWN = 1;  
    /** 
     * 動畫完成 
     */  
    public static final int DONE = 2;  
    /** 
     * 動畫速度 
     */  
    public static final float SPEED = 6.5f;  
  
    private boolean isMeasured = false;  
  
    /** 
     * 用於計算手滑動的速度 
     */  
    private VelocityTracker vt;  
  
    private int mViewHeight;  
    private int mViewWidth;  
  
    private View topView;  
    private View bottomView;  
  
    private boolean canPullDown;  
    private boolean canPullUp;  
    private int state = DONE;  
  
    /** 
     * 記錄當前展示的是哪個view,0是topView,1是bottomView 
     */  
    private int mCurrentViewIndex = 0;  
    /** 
     * 手滑動距離,這個是控制布局的主要變量 
     */  
    private float mMoveLen;  
    private MyTimer mTimer;  
    private float mLastY;  
    /** 
     * 用於控制是否變動布局的另一個條件,mEvents==0時布局可以拖拽了,mEvents==-1時可以舍棄將要到來的第一個move事件, 
     * 這點是去除多點拖動劇變的關鍵 
     */  
    private int mEvents;  
  
    private Handler handler = new Handler() {  
  
        @Override  
        public void handleMessage(Message msg) {  
            if (mMoveLen != 0) {  
                if (state == AUTO_UP) {  
                    mMoveLen -= SPEED;  
                    if (mMoveLen <= -mViewHeight) {  
                        mMoveLen = -mViewHeight;  
                        state = DONE;  
                        mCurrentViewIndex = 1;  
                    }  
                } else if (state == AUTO_DOWN) {  
                    mMoveLen += SPEED;  
                    if (mMoveLen >= 0) {  
                        mMoveLen = 0;  
                        state = DONE;  
                        mCurrentViewIndex = 0;  
                    }  
                } else {  
                    mTimer.cancel();  
                }  
            }  
            requestLayout();  
        }  
  
    };  
  
    public ScrollViewContainer(Context context) {  
        super(context);  
        init();  
    }  
  
    public ScrollViewContainer(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        init();  
    }  
  
    public ScrollViewContainer(Context context, AttributeSet attrs, int defStyle) {  
        super(context, attrs, defStyle);  
        init();  
    }  
  
    private void init() {  
        mTimer = new MyTimer(handler);  
    }  
  
    @Override  
    public boolean dispatchTouchEvent(MotionEvent ev) {  
        switch (ev.getActionMasked()) {  
        case MotionEvent.ACTION_DOWN:  
            if (vt == null)  
                vt = VelocityTracker.obtain();  
            else  
                vt.clear();  
            mLastY = ev.getY();  
            vt.addMovement(ev);  
            mEvents = 0;  
            break;  
        case MotionEvent.ACTION_POINTER_DOWN:  
        case MotionEvent.ACTION_POINTER_UP:  
            // 多一只手指按下或抬起時舍棄將要到來的第一個事件move,防止多點拖拽的bug  
            mEvents = -1;  
            break;  
        case MotionEvent.ACTION_MOVE:  
            vt.addMovement(ev);  
            if (canPullUp && mCurrentViewIndex == 0 && mEvents == 0) {  
                mMoveLen += (ev.getY() - mLastY);  
                // 防止上下越界  
                if (mMoveLen > 0) {  
                    mMoveLen = 0;  
                    mCurrentViewIndex = 0;  
                } else if (mMoveLen < -mViewHeight) {  
                    mMoveLen = -mViewHeight;  
                    mCurrentViewIndex = 1;  
  
                }  
                if (mMoveLen < -8) {  
                    // 防止事件沖突  
                    ev.setAction(MotionEvent.ACTION_CANCEL);  
                }  
            } else if (canPullDown && mCurrentViewIndex == 1 && mEvents == 0) {  
                mMoveLen += (ev.getY() - mLastY);  
                // 防止上下越界  
                if (mMoveLen < -mViewHeight) {  
                    mMoveLen = -mViewHeight;  
                    mCurrentViewIndex = 1;  
                } else if (mMoveLen > 0) {  
                    mMoveLen = 0;  
                    mCurrentViewIndex = 0;  
                }  
                if (mMoveLen > 8 - mViewHeight) {  
                    // 防止事件沖突  
                    ev.setAction(MotionEvent.ACTION_CANCEL);  
                }  
            } else  
                mEvents++;  
            mLastY = ev.getY();  
            requestLayout();  
            break;  
        case MotionEvent.ACTION_UP:  
            mLastY = ev.getY();  
            vt.addMovement(ev);  
            vt.computeCurrentVelocity(700);  
            // 獲取Y方向的速度  
            float mYV = vt.getYVelocity();  
            if (mMoveLen == 0 || mMoveLen == -mViewHeight)  
                break;  
            if (Math.abs(mYV) < 500) {  
                // 速度小於一定值的時候當作靜止釋放,這時候兩個View往哪移動取決於滑動的距離  
                if (mMoveLen <= -mViewHeight / 2) {  
                    state = AUTO_UP;  
                } else if (mMoveLen > -mViewHeight / 2) {  
                    state = AUTO_DOWN;  
                }  
            } else {  
                // 抬起手指時速度方向決定兩個View往哪移動  
                if (mYV < 0)  
                    state = AUTO_UP;  
                else  
                    state = AUTO_DOWN;  
            }  
            mTimer.schedule(2);  
            try {  
                vt.recycle();  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            break;  
  
        }  
        super.dispatchTouchEvent(ev);  
        return true;  
    }  
  
    @Override  
    protected void onLayout(boolean changed, int l, int t, int r, int b) {  
        topView.layout(0, (int) mMoveLen, mViewWidth,  
                topView.getMeasuredHeight() + (int) mMoveLen);  
        bottomView.layout(0, topView.getMeasuredHeight() + (int) mMoveLen,  
                mViewWidth, topView.getMeasuredHeight() + (int) mMoveLen  
                        + bottomView.getMeasuredHeight());  
    }  
  
    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
        if (!isMeasured) {  
            isMeasured = true;  
  
            mViewHeight = getMeasuredHeight();  
            mViewWidth = getMeasuredWidth();  
  
            topView = getChildAt(0);  
            bottomView = getChildAt(1);  
  
            bottomView.setOnTouchListener(bottomViewTouchListener);  
            topView.setOnTouchListener(topViewTouchListener);  
        }  
    }  
  
    private OnTouchListener topViewTouchListener = new OnTouchListener() {  
  
        @Override  
        public boolean onTouch(View v, MotionEvent event) {  
            ScrollView sv = (ScrollView) v;  
            if (sv.getScrollY() == (sv.getChildAt(0).getMeasuredHeight() - sv  
                    .getMeasuredHeight()) && mCurrentViewIndex == 0)  
                canPullUp = true;  
            else  
                canPullUp = false;  
            return false;  
        }  
    };  
    private OnTouchListener bottomViewTouchListener = new OnTouchListener() {  
  
        @Override  
        public boolean onTouch(View v, MotionEvent event) {  
            ScrollView sv = (ScrollView) v;  
            if (sv.getScrollY() == 0 && mCurrentViewIndex == 1)  
                canPullDown = true;  
            else  
                canPullDown = false;  
            return false;  
        }  
    };  
  
    class MyTimer {  
        private Handler handler;  
        private Timer timer;  
        private MyTask mTask;  
  
        public MyTimer(Handler handler) {  
            this.handler = handler;  
            timer = new Timer();  
        }  
  
        public void schedule(long period) {  
            if (mTask != null) {  
                mTask.cancel();  
                mTask = null;  
            }  
            mTask = new MyTask(handler);  
            timer.schedule(mTask, 0, period);  
        }  
  
        public void cancel() {  
            if (mTask != null) {  
                mTask.cancel();  
                mTask = null;  
            }  
        }  
  
        class MyTask extends TimerTask {  
            private Handler handler;  
  
            public MyTask(Handler handler) {  
                this.handler = handler;  
            }  
  
            @Override  
            public void run() {  
                handler.obtainMessage().sendToTarget();  
            }  
  
        }  
    }  
  
}  

 


注釋寫的很清楚了,有幾個關鍵點需要講一下:

 

    1、由於這里為兩個ScrollView設置了OnTouchListener,所以在其他地方不能再設置了,否則就白搭了。

    2、兩個ScrollView的layout參數統一由mMoveLen決定。

    3、變量mEvents有兩個作用:一是防止手動滑到底部或頂部時繼續滑動而改變布局,必須再次按下才能繼續滑動;二是在新的pointer down或up時把mEvents設置成-1可以舍棄將要到來的第一個move事件,防止mMoveLen出現劇變。為什么會出現劇變呢?因為假設一開始只有一只手指在滑動,記錄的坐標值是這個pointer的事件坐標點,這時候另一只手指按下了導致事件又多了一個pointer,這時候到來的move事件的坐標可能就變成了新的pointer的坐標,這時計算與上一次坐標的差值就會出現劇變,變化的距離就是兩個pointer間的距離。所以要把這個move事件舍棄掉,讓mLastY值記錄這個pointer的坐標再開始計算mMoveLen。pointer up的時候也一樣。

理解了這幾點,看起來就沒什么難度了,代碼量也很小。

MainActivity的布局:

  

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" >  
  
    <com.jingchen.tbviewer.ScrollViewContainer  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" >  
  
        <ScrollView  
            android:layout_width="match_parent"  
            android:layout_height="match_parent" >  
  
            <RelativeLayout  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content" >  
  
                <LinearLayout  
                    android:id="@+id/imagesLayout"  
                    android:layout_width="match_parent"  
                    android:layout_height="wrap_content"  
                    android:gravity="center_horizontal"  
                    android:orientation="vertical" >  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/h" />  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/i" />  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/j" />  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/k" />  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/l" />  
  
                    <ImageView  
                        android:layout_width="wrap_content"  
                        android:layout_height="wrap_content"  
                        android:background="@drawable/m" />  
                </LinearLayout>  
  
                <TextView  
                    android:layout_width="match_parent"  
                    android:layout_height="60dp"  
                    android:layout_below="@id/imagesLayout"  
                    android:background="#eeeeee"  
                    android:gravity="center"  
                    android:text="繼續拖動,查看更多美女"  
                    android:textSize="20sp" />  
            </RelativeLayout>  
        </ScrollView>  
  
        <ScrollView  
            android:layout_width="match_parent"  
            android:layout_height="match_parent"  
            android:background="#000000" >  
  
            <LinearLayout  
                android:layout_width="match_parent"  
                android:layout_height="match_parent"  
                android:gravity="center_horizontal"  
                android:orientation="vertical" >  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/a" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/b" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/c" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/d" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/e" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/f" />  
  
                <ImageView  
                    android:layout_width="wrap_content"  
                    android:layout_height="wrap_content"  
                    android:background="@drawable/g" />  
            </LinearLayout>  
        </ScrollView>  
    </com.jingchen.tbviewer.ScrollViewContainer>  
  
</RelativeLayout>  

 

 

在ScrollView中放了幾張圖片而已。

 

MainActivity的代碼:

  

package com.jingchen.tbviewer;  
  
import android.app.Activity;  
import android.os.Bundle;  
import android.view.Menu;  
  
public class MainActivity extends Activity  
{  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
    }  
  
    @Override  
    public boolean onCreateOptionsMenu(Menu menu)  
    {  
        getMenuInflater().inflate(R.menu.main, menu);  
        return true;  
    }  
  
}  

 

啥也沒有......

 

好了,到此結束~

源碼下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM