編輯:Android開發實例
上一篇中,滑動式菜單的初步效果已實現,這篇繼續完善和優化。打個比方:就拿蓋房子來說,上一篇完成的只是毛胚房,這篇要做的就是對毛胚房進行精美裝修,之後才可以使用。好了,進入正題,假設ViewGroup中有兩個子View A和B,B處於A上面,兩個子View是疊在一起的。默認顯示的是B,並占據著整個手機屏幕,A是看不見的。為了能看見A,並且可以操作,我們需要把B視圖(子View)移動一定單位,當A、B均可見並都可以接受事件。此時B視圖可見部分就是手柄的寬度。
存在的問題:
1、當長按B視圖的可見部分(手柄),會發現會觸發A視圖的事件,這顯然是不行的。用戶明明在B視圖上操作,響應的卻是A視圖中的子View。
2、當在B視圖的可見部分(手柄)上,用手指向右滑動時,發現B是還可以向右滑動。
3、當B視圖占據著整個手機屏幕時,在B視圖的任何區域水平滑動都可以讓B向右滑動,也就說觸控區域太大,會引起誤操作。
4、手指水平滑動,響應很遲鈍,靈敏度問題。
解決辦法:
添加觸摸事件分發處理回調方法dispatchTouchEvent(MotionEvent ev),其返回值決定事件的分發給誰處理。當用戶手指在屏幕上按下後,閱讀下面代碼片段:
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "dispatchTouchEvent(): ACTION_DOWN");
- mFinished = mScroller.isFinished();
- if(mFinished){
- int x = (int) ev.getX();
- int width = getWidth();
- if(mPanelInvisible)// 左側面板可見
- {
- if(x > (width - mHandlebarWidth)){ // 當前手指按下的坐標x值 ,是在手柄寬度范圍內
- isClick = true;
- mAllowScroll = true;
- return true;
- } else {
- isClick = false;
- mAllowScroll = false;
- }
- } else { // 左側面板不可見
- if(x < mHandlebarWidth ){ // 當前手指按下的坐標x值 < 手柄寬度 (也就是說在手柄寬度范圍內,是可以相應用戶的向右滑動手勢)
- mAllowScroll = true;
- }else{
- mAllowScroll = false;
- }
- }
- } else {
- // 當前正在滾動子View,其它的事不響應
- return false;
- }
- break;
當前手指按下的坐標x值 ,是在手柄寬度范圍內,改變標識值,返回true。表示onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)中的MotionEvent.ACTION_DOWN事件不再處理。
上一個Action down事件處理完後,接下來響應dispatchTouchEvent(MotionEvent ev)的MotionEvent.ACTION_MOVE,代碼如下:
- case MotionEvent.ACTION_MOVE:
- Log.i(TAG, "dispatchTouchEvent(): ACTION_MOVE");
- int margin = getWidth() - (int) ev.getX();
- if (margin < mHandlebarWidth && mAllowScroll) {
- Log.e(TAG, "dispatchTouchEvent ACTION_MOVE margin = " + margin + "\t mHandlebarWidth = " + mHandlebarWidth);
- return true;
- }
- break;
在MotionEvent.ACTION_MOVE代碼塊中,發現條件是滿足的,返回true。表示和MotionEvent.ACTION_DOWN的一樣,在後續的回調方法內Action move將不再處理。最後看當手指抬起,處理的代碼如下:
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "dispatchTouchEvent(): ACTION_UP");
- if (isClick && mPanelInvisible && mAllowScroll) {
- isClick = false;
- mPanelInvisible = false;
- int scrollX = getChildAt(1).getScrollX();
- mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);
- invalidate();
- return true;
- }
- break;
上面的代碼片段含義:當用戶手指在屏幕上按下,當前ViewGroup中沒有子View正在滾動,左側面板處於可見,手指按下的坐標X值是在手柄(B視圖的當前可見部分)的寬度范圍內,則down事件到此處理結束。在move代碼塊裡同樣判斷用戶當前手指按下的坐標X值是在手柄寬度范圍內的,則move事件也到此結束。在手指抬起後,up事件裡判斷在down是事件裡設置的標識,發現條件都滿足,響應用戶在手柄上的單擊事件請求。也就是說父View把用戶的請求已響應了,不用傳遞到子View。子View也不知道發生過這件事。(大概意思就是這樣)
觸控區域太大的問題,每次都判斷用戶手指按下的坐標x值,是否在手柄寬度范圍內(不管左側面板是否可見)。靈敏度的問題,其實就是響應滾動子View的臨界值的大小問題,值越小靈敏都越高。
ScrollerContainer類,修改後的代碼如下:
- package com.everyone.android.widget;
- import android.content.Context;
- import android.util.Log;
- import android.util.TypedValue;
- import android.view.MotionEvent;
- import android.view.VelocityTracker;
- import android.view.ViewConfiguration;
- import android.widget.RelativeLayout;
- import android.widget.Scroller;
- /**
- * 功能描述:手指在屏幕上左右滑動時,該類的實例負責讓其子View根據用戶的手勢左右偏移(滾動)
- *
- * @author android_ls
- */
- public class ScrollerContainer extends RelativeLayout {
- private static final String TAG = "ScrollerContainer";
- private Scroller mScroller;
- private VelocityTracker mVelocityTracker;
- /**
- * 手柄(手把)的寬度
- */
- private int mHandlebarWidth;
- /**
- * 在偏移過程中,動畫持續的時間
- */
- private static final int ANIMATION_DURATION_TIME = 300;
- /**
- * 記錄當前的滑動結束後的狀態,左側面板是否可見
- * true 向右滑動(左側面板處於可見)
- * false 向左滑動(左側面板處於不可見)
- */
- private boolean mPanelInvisible;
- /**
- * 是否已滑動結束
- */
- private boolean mFinished;
- /**
- * 是否允許滾動
- * 滿足的條件:
- * 左側面板可見,當前手指按下的坐標x值 ,是在手柄寬度范圍內;
- * 左側面板不可見,當前手指按下的坐標x值 < 手柄寬度
- */
- private boolean mAllowScroll;
- /**
- * 是否滿足響應單擊事件的條件
- * 滿足的條件:左側面板可見,當前手指按下的坐標x值 ,是在手柄寬度范圍內
- */
- private boolean isClick;
- public ScrollerContainer(Context context) {
- super(context);
- mScroller = new Scroller(context);
- mHandlebarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources().getDisplayMetrics());
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.e(TAG, "dispatchTouchEvent()");
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "dispatchTouchEvent(): ACTION_DOWN");
- mFinished = mScroller.isFinished();
- if(mFinished){
- int x = (int) ev.getX();
- int width = getWidth();
- if(mPanelInvisible)// 左側面板可見
- {
- if(x > (width - mHandlebarWidth)){ // 當前手指按下的坐標x值 ,是在手柄寬度范圍內
- isClick = true;
- mAllowScroll = true;
- return true;
- } else {
- isClick = false;
- mAllowScroll = false;
- }
- } else { // 左側面板不可見
- if(x < mHandlebarWidth ){ // 當前手指按下的坐標x值 < 手柄寬度 (也就是說在手柄寬度范圍內,是可以相應用戶的向右滑動手勢)
- mAllowScroll = true;
- }else{
- mAllowScroll = false;
- }
- }
- } else {
- // 當前正在滾動子View,其它的事不響應
- return false;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- Log.i(TAG, "dispatchTouchEvent(): ACTION_MOVE");
- int margin = getWidth() - (int) ev.getX();
- if (margin < mHandlebarWidth && mAllowScroll) {
- Log.e(TAG, "dispatchTouchEvent ACTION_MOVE margin = " + margin + "\t mHandlebarWidth = " + mHandlebarWidth);
- return true;
- }
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "dispatchTouchEvent(): ACTION_UP");
- if (isClick && mPanelInvisible && mAllowScroll) {
- isClick = false;
- mPanelInvisible = false;
- int scrollX = getChildAt(1).getScrollX();
- mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);
- invalidate();
- return true;
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.i(TAG, "dispatchTouchEvent(): ACTION_CANCEL");
- break;
- default:
- break;
- }
- return super.dispatchTouchEvent(ev);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Log.e(TAG, "onInterceptTouchEvent()");
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_DOWN");
- mFinished = mScroller.isFinished();
- if(!mFinished){
- return false;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_MOVE");
- mVelocityTracker = VelocityTracker.obtain();
- mVelocityTracker.addMovement(ev);
- // 一秒時間內移動了多少個像素
- mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity());
- float velocityValue = Math.abs(mVelocityTracker.getXVelocity()) ;
- Log.d(TAG, "onInterceptTouchEvent(): mVelocityValue = " + velocityValue);
- if (velocityValue > 300 && mAllowScroll) {
- return true;
- }
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_UP");
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.i(TAG, "onInterceptTouchEvent(): ACTION_CANCEL");
- break;
- default:
- break;
- }
- return super.onInterceptTouchEvent(ev);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(TAG, "onTouchEvent()");
- float x = event.getX();
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "onTouchEvent(): ACTION_DOWN");
- mFinished = mScroller.isFinished();
- if(!mFinished){
- return false;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- Log.i(TAG, "onTouchEvent(): ACTION_MOVE");
- getChildAt(1).scrollTo(-(int)x, 0);
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "onTouchEvent(): ACTION_UP");
- if(!mAllowScroll){
- break;
- }
- float width = getWidth();
- // 響應滾動子View的臨界值,若覺得響應過於靈敏,可以將只改大些。
- // 比如:criticalWidth = width / 3或criticalWidth = width / 2,看情況而定,呵呵。
- float criticalWidth = width / 5;
- Log.i(TAG, "onTouchEvent(): ACTION_UP x = " + x + "\t criticalWidth = " + criticalWidth);
- int scrollX = getChildAt(1).getScrollX();
- if ( x < criticalWidth) {
- Log.i(TAG, "onTouchEvent(): ACTION_UP 向左滑動");
- mPanelInvisible = false;
- mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);
- invalidate();
- } else if ( x > criticalWidth){
- Log.i(TAG, "onTouchEvent(): ACTION_UP 向右滑動");
- mPanelInvisible = true;
- int toX = (int)(width - Math.abs(scrollX) - mHandlebarWidth);
- mScroller.startScroll(scrollX, 0, -toX, 0, ANIMATION_DURATION_TIME);
- invalidate();
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.i(TAG, "onTouchEvent(): ACTION_CANCEL");
- break;
- default:
- break;
- }
- return super.onTouchEvent(event);
- }
- @Override
- public void computeScroll() {
- // super.computeScroll();
- if(mScroller.computeScrollOffset()){
- this.getChildAt(1).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
- this.postInvalidate();
- }
- }
- /**
- * 向右滑動View,讓左側操作面飯可見
- */
- public void slideToRight() {
- mFinished = mScroller.isFinished();
- if(mFinished && !mPanelInvisible){
- mPanelInvisible = true;
- float width = getWidth();
- int scrollX = getChildAt(1).getScrollX();
- int toX = (int)(width - Math.abs(scrollX) - mHandlebarWidth);
- mScroller.startScroll(scrollX, 0, -toX, 0, ANIMATION_DURATION_TIME);
- invalidate();
- }
- }
- /**
- * View滑動事件監聽器
- * @author android_ls
- */
- public interface OnSlideListener {
- /**
- * 向左滑動子View
- */
- public abstract void toLeft();
- /**
- * 向右滑動子View
- */
- public abstract void toRight();
- }
- }
主應用界面源碼:
- package com.everyone.android.ui;
- import android.os.Bundle;
- import android.view.ViewGroup.LayoutParams;
- import com.everyone.android.AppBaseActivity;
- import com.everyone.android.widget.FreshNewsLayout;
- import com.everyone.android.widget.LeftPanelLayout;
- import com.everyone.android.widget.ScrollerContainer;
- import com.everyone.android.widget.ScrollerContainer.OnSlideListener;
- /**
- * 功能描述:應用主界面
- * @author android_ls
- *
- */
- public class EveryoneActivity extends AppBaseActivity implements OnSlideListener {
- /**
- * 滾動(滑動)容器
- */
- private ScrollerContainer mSlideContainer;
- /**
- * 左側面板
- */
- private LeftPanelLayout mLeftPanelLayout;
- /**
- * 新鮮事
- */
- private FreshNewsLayout mFreshNewsLayout;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(mSlideContainer);
- }
- @Override
- protected int getLayoutId() {
- return 0;
- }
- @Override
- protected void setupView() {
- mSlideContainer = new ScrollerContainer(mContext);
- mLeftPanelLayout = new LeftPanelLayout(mContext);
- mFreshNewsLayout = new FreshNewsLayout(mContext);
- mFreshNewsLayout.setOnSlideListener(this);
- LayoutParams layoutParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
- mSlideContainer.addView(mLeftPanelLayout, layoutParams);
- mSlideContainer.addView(mFreshNewsLayout, layoutParams);
- }
- @Override
- protected void initialized() {
- // TODO Auto-generated method stub
- }
- @Override
- public void toLeft() {
- // TODO Auto-generated method stub
- }
- @Override
- public void toRight() {
- mSlideContainer.slideToRight();
- }
- }
AppBaseActivity類的修改部分,在onCreate裡添加的處理:
- int layoutId = getLayoutId();
- if(layoutId != 0){
- setContentView(getLayoutId());
- }
有關滑動菜單的到這裡就完了,後面在使用過程中遇到什麼問題,再處理。
看下效果圖,還和上一篇一樣,靜態的圖片看不出來優化後的效果,不過還是上傳幾張,有圖有真相。
向右滑動或點擊頂部箭頭後
轉自:http://blog.csdn.net/android_ls/article/details/8758943
Google的開源Android移動操作系統正在席卷全球智能手機市場,和蘋果不一樣,它對那些想將應用程序提交到iPhone App Store的開發人員有著嚴格的
在項目開發中,可能系統自帶的一些widget不能滿足我們的需求,這時就需要自定義View。 通過查看系統中的常用widget如Button,TextView,Ed
在很多方面,藍牙是一種能夠發送或接受兩個不同的設備之間傳輸的數據。 Android平台包含了藍牙框架,使設備以無線方式與其他藍牙設備進行數據交換的支持。Android提供
前面文章介紹了Android利用麥克風采集並顯示模擬信號的實現方法,這種采集手段適用於無IO控制、單純讀取信號的情況。如果傳感器本身需要包含控制電路(例如采集血氧