Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義控件實戰——仿淘寶商品浏覽界面

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

編輯:關於Android編程

 

用手機淘寶浏覽商品詳情時,商品圖片是放在後面的,在第一個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的布局:

 



    

        

            

                

                    

                    

                    

                    

                    

                    
                

                
            
        

        

            

                

                

                

                

                

                

                
            
        
    


在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;
	}

}

啥也沒有......

 

好了,到此結束~

源碼下載

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved