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,明白了原理之后可以看代码了: **[java]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38656929#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38656929#) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="keyword">package</span> com.jingchen.tbviewer; - - <span class="keyword">import</span> java.util.Timer; - <span class="keyword">import</span> java.util.TimerTask; - - <span class="keyword">import</span> android.content.Context; - <span class="keyword">import</span> android.os.Handler; - <span class="keyword">import</span> android.os.Message; - <span class="keyword">import</span> android.util.AttributeSet; - <span class="keyword">import</span> android.view.MotionEvent; - <span class="keyword">import</span> android.view.VelocityTracker; - <span class="keyword">import</span> android.view.View; - <span class="keyword">import</span> android.widget.RelativeLayout; - <span class="keyword">import</span> android.widget.ScrollView; - - <span class="comment">/**</span> - <span class="comment"> * 包含两个ScrollView的容器</span> - <span class="comment"> * </span> - <span class="comment"> * @author chenjing</span> - <span class="comment"> * </span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">class</span> ScrollViewContainer <span class="keyword">extends</span> RelativeLayout { - - <span class="comment">/**</span> - <span class="comment"> * 自动上滑</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> AUTO_UP = <span class="number"></span>; - <span class="comment">/**</span> - <span class="comment"> * 自动下滑</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> AUTO_DOWN = <span class="number">1</span>; - <span class="comment">/**</span> - <span class="comment"> * 动画完成</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DONE = <span class="number">2</span>; - <span class="comment">/**</span> - <span class="comment"> * 动画速度</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">float</span> SPEED = <span class="number">6</span>.5f; - - <span class="keyword">private</span> <span class="keyword">boolean</span> isMeasured = <span class="keyword">false</span>; - - <span class="comment">/**</span> - <span class="comment"> * 用于计算手滑动的速度</span> - <span class="comment"> */</span> - <span class="keyword">private</span> VelocityTracker vt; - - <span class="keyword">private</span> <span class="keyword">int</span> mViewHeight; - <span class="keyword">private</span> <span class="keyword">int</span> mViewWidth; - - <span class="keyword">private</span> View topView; - <span class="keyword">private</span> View bottomView; - - <span class="keyword">private</span> <span class="keyword">boolean</span> canPullDown; - <span class="keyword">private</span> <span class="keyword">boolean</span> canPullUp; - <span class="keyword">private</span> <span class="keyword">int</span> state = DONE; - - <span class="comment">/**</span> - <span class="comment"> * 记录当前展示的是哪个view,0是topView,1是bottomView</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mCurrentViewIndex = <span class="number"></span>; - <span class="comment">/**</span> - <span class="comment"> * 手滑动距离,这个是控制布局的主要变量</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">float</span> mMoveLen; - <span class="keyword">private</span> MyTimer mTimer; - <span class="keyword">private</span> <span class="keyword">float</span> mLastY; - <span class="comment">/**</span> - <span class="comment"> * 用于控制是否变动布局的另一个条件,mEvents==0时布局可以拖拽了,mEvents==-1时可以舍弃将要到来的第一个move事件,</span> - <span class="comment"> * 这点是去除多点拖动剧变的关键</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mEvents; - - <span class="keyword">private</span> Handler handler = <span class="keyword">new</span> Handler() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> handleMessage(Message msg) { - <span class="keyword">if</span> (mMoveLen != <span class="number"></span>) { - <span class="keyword">if</span> (state == AUTO_UP) { - mMoveLen -= SPEED; - <span class="keyword">if</span> (mMoveLen <= -mViewHeight) { - mMoveLen = -mViewHeight; - state = DONE; - mCurrentViewIndex = <span class="number">1</span>; - } - } <span class="keyword">else</span> <span class="keyword">if</span> (state == AUTO_DOWN) { - mMoveLen += SPEED; - <span class="keyword">if</span> (mMoveLen >= <span class="number"></span>) { - mMoveLen = <span class="number"></span>; - state = DONE; - mCurrentViewIndex = <span class="number"></span>; - } - } <span class="keyword">else</span> { - mTimer.cancel(); - } - } - requestLayout(); - } - - }; - - <span class="keyword">public</span> ScrollViewContainer(Context context) { - <span class="keyword">super</span>(context); - init(); - } - - <span class="keyword">public</span> ScrollViewContainer(Context context, AttributeSet attrs) { - <span class="keyword">super</span>(context, attrs); - init(); - } - - <span class="keyword">public</span> ScrollViewContainer(Context context, AttributeSet attrs, <span class="keyword">int</span> defStyle) { - <span class="keyword">super</span>(context, attrs, defStyle); - init(); - } - - <span class="keyword">private</span> <span class="keyword">void</span> init() { - mTimer = <span class="keyword">new</span> MyTimer(handler); - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">boolean</span> dispatchTouchEvent(MotionEvent ev) { - <span class="keyword">switch</span> (ev.getActionMasked()) { - <span class="keyword">case</span> MotionEvent.ACTION_DOWN: - <span class="keyword">if</span> (vt == <span class="keyword">null</span>) - vt = VelocityTracker.obtain(); - <span class="keyword">else</span> - vt.clear(); - mLastY = ev.getY(); - vt.addMovement(ev); - mEvents = <span class="number"></span>; - <span class="keyword">break</span>; - <span class="keyword">case</span> MotionEvent.ACTION_POINTER_DOWN: - <span class="keyword">case</span> MotionEvent.ACTION_POINTER_UP: - <span class="comment">// 多一只手指按下或抬起时舍弃将要到来的第一个事件move,防止多点拖拽的bug</span> - <span class="keyword">break</span>; - <span class="keyword">case</span> MotionEvent.ACTION_MOVE: - vt.addMovement(ev); - <span class="keyword">if</span> (canPullUp && mCurrentViewIndex == <span class="number"></span> && mEvents == <span class="number"></span>) { - <span class="comment">// 防止上下越界</span> - <span class="keyword">if</span> (mMoveLen > <span class="number"></span>) { - mMoveLen = <span class="number"></span>; - mCurrentViewIndex = <span class="number"></span>; - } <span class="keyword">else</span> <span class="keyword">if</span> (mMoveLen < -mViewHeight) { - mMoveLen = -mViewHeight; - mCurrentViewIndex = <span class="number">1</span>; - - } - <span class="comment">// 防止事件冲突</span> - ev.setAction(MotionEvent.ACTION_CANCEL); - } - } <span class="keyword">else</span> <span class="keyword">if</span> (canPullDown && mCurrentViewIndex == <span class="number">1</span> && mEvents == <span class="number"></span>) { - <span class="comment">// 防止上下越界</span> - <span class="keyword">if</span> (mMoveLen < -mViewHeight) { - mMoveLen = -mViewHeight; - mCurrentViewIndex = <span class="number">1</span>; - } <span class="keyword">else</span> <span class="keyword">if</span> (mMoveLen > <span class="number"></span>) { - mMoveLen = <span class="number"></span>; - mCurrentViewIndex = <span class="number"></span>; - } - <span class="comment">// 防止事件冲突</span> - ev.setAction(MotionEvent.ACTION_CANCEL); - } - } <span class="keyword">else</span> - mEvents++; - mLastY = ev.getY(); - requestLayout(); - <span class="keyword">break</span>; - <span class="keyword">case</span> MotionEvent.ACTION_UP: - mLastY = ev.getY(); - vt.addMovement(ev); - vt.computeCurrentVelocity(<span class="number">700</span>); - <span class="comment">// 获取Y方向的速度</span> - <span class="keyword">float</span> mYV = vt.getYVelocity(); - <span class="keyword">if</span> (mMoveLen == <span class="number"></span> || mMoveLen == -mViewHeight) - <span class="keyword">break</span>; - <span class="keyword">if</span> (Math.abs(mYV) < <span class="number">500</span>) { - <span class="comment">// 速度小于一定值的时候当作静止释放,这时候两个View往哪移动取决于滑动的距离</span> - <span class="keyword">if</span> (mMoveLen <= -mViewHeight / <span class="number">2</span>) { - state = AUTO_UP; - } <span class="keyword">else</span> <span class="keyword">if</span> (mMoveLen > -mViewHeight / <span class="number">2</span>) { - state = AUTO_DOWN; - } - } <span class="keyword">else</span> { - <span class="comment">// 抬起手指时速度方向决定两个View往哪移动</span> - <span class="keyword">if</span> (mYV < <span class="number"></span>) - state = AUTO_UP; - <span class="keyword">else</span> - state = AUTO_DOWN; - } - mTimer.schedule(<span class="number">2</span>); - <span class="keyword">try</span> { - vt.recycle(); - } <span class="keyword">catch</span> (Exception e) { - e.printStackTrace(); - } - <span class="keyword">break</span>; - - } - <span class="keyword">super</span>.dispatchTouchEvent(ev); - <span class="keyword">return</span> <span class="keyword">true</span>; - } - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onLayout(<span class="keyword">boolean</span> changed, <span class="keyword">int</span> l, <span class="keyword">int</span> t, <span class="keyword">int</span> r, <span class="keyword">int</span> b) { - topView.layout(<span class="number"></span>, (<span class="keyword">int</span>) mMoveLen, mViewWidth, - topView.getMeasuredHeight() + (<span class="keyword">int</span>) mMoveLen); - bottomView.layout(<span class="number"></span>, topView.getMeasuredHeight() + (<span class="keyword">int</span>) mMoveLen, - mViewWidth, topView.getMeasuredHeight() + (<span class="keyword">int</span>) mMoveLen - + bottomView.getMeasuredHeight()); - } - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onMeasure(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec) { - <span class="keyword">super</span>.onMeasure(widthMeasureSpec, heightMeasureSpec); - <span class="keyword">if</span> (!isMeasured) { - isMeasured = <span class="keyword">true</span>; - - mViewHeight = getMeasuredHeight(); - mViewWidth = getMeasuredWidth(); - - topView = getChildAt(<span class="number"></span>); - bottomView = getChildAt(<span class="number">1</span>); - - bottomView.setOnTouchListener(bottomViewTouchListener); - topView.setOnTouchListener(topViewTouchListener); - } - } - - <span class="keyword">private</span> OnTouchListener topViewTouchListener = <span class="keyword">new</span> OnTouchListener() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">boolean</span> onTouch(View v, MotionEvent event) { - ScrollView sv = (ScrollView) v; - .getMeasuredHeight()) && mCurrentViewIndex == <span class="number"></span>) - canPullUp = <span class="keyword">true</span>; - <span class="keyword">else</span> - canPullUp = <span class="keyword">false</span>; - <span class="keyword">return</span> <span class="keyword">false</span>; - } - }; - <span class="keyword">private</span> OnTouchListener bottomViewTouchListener = <span class="keyword">new</span> OnTouchListener() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">boolean</span> onTouch(View v, MotionEvent event) { - ScrollView sv = (ScrollView) v; - <span class="keyword">if</span> (sv.getScrollY() == <span class="number"></span> && mCurrentViewIndex == <span class="number">1</span>) - canPullDown = <span class="keyword">true</span>; - <span class="keyword">else</span> - canPullDown = <span class="keyword">false</span>; - <span class="keyword">return</span> <span class="keyword">false</span>; - } - }; - - <span class="keyword">class</span> MyTimer { - <span class="keyword">private</span> Handler handler; - <span class="keyword">private</span> Timer timer; - <span class="keyword">private</span> MyTask mTask; - - <span class="keyword">public</span> MyTimer(Handler handler) { - <span class="keyword">this</span>.handler = handler; - timer = <span class="keyword">new</span> Timer(); - } - - <span class="keyword">public</span> <span class="keyword">void</span> schedule(<span class="keyword">long</span> period) { - <span class="keyword">if</span> (mTask != <span class="keyword">null</span>) { - mTask.cancel(); - mTask = <span class="keyword">null</span>; - } - mTask = <span class="keyword">new</span> MyTask(handler); - timer.schedule(mTask, <span class="number"></span>, period); - } - - <span class="keyword">public</span> <span class="keyword">void</span> cancel() { - <span class="keyword">if</span> (mTask != <span class="keyword">null</span>) { - mTask.cancel(); - mTask = <span class="keyword">null</span>; - } - } - - <span class="keyword">class</span> MyTask <span class="keyword">extends</span> TimerTask { - <span class="keyword">private</span> Handler handler; - - <span class="keyword">public</span> MyTask(Handler handler) { - <span class="keyword">this</span>.handler = handler; - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> run() { - handler.obtainMessage().sendToTarget(); - } - - } - } - - } 注释写的很清楚了,有几个关键点需要讲一下: ...