第13章 CustomView控件高級屬性

一、GestureDetector手勢檢測

概述:

當用戶觸摸屏幕的時候,會產生許多手勢,如 down、up 、scroll 、fling等。

GestureDetector(手勢檢測)類,通過這個類可以識別很多手勢。在識別出於勢之後,具體的事務處理則交 由程序員自己來實現。

 

GestureDetector.OnGestureListener接口:

1.基本講解

如果我們寫一個類並繼承自OnGestureListener,則會提示有幾個必須重寫的函數。

private class gesturelistener implements GestureDetector.OnGestureListener {

    @Override
    public boolean onDown(MotionEvent e) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {
        // TODO Auto-generated method stub
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
        // TODO Auto-generated method stub
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        // TODO Auto-generated method stub
        return false;
    }
}

這裏重寫 了 6 個函數,這些函數在什麼情況下才會被觸發呢?
• onDown(MotionEvent e):用戶按下屏幕就會觸發該函數。
• onShowPress(MotionEvent e):如果按下的時間超過瞬間,而且在按下的時候沒有鬆開或者是拖動的,該函數就會被觸發。
• onLongPress(MotionEvent e):長接觸摸屏,超過一定時長,就會觸發這個函數。

觸發順序:onDown→onShowPress→onLongPress

• onSingleTapUp(MotionEvent e): 從名字中也可 以看出,一次單獨的輕擊擡起操作,也就是輕擊一下屏幕,立刻擡起來,纔會觸發這個函數。當然,如果除down以外還有其他操作,就不再算是單獨操作了,也就不會觸發這個函數了。
單擊一下非常快的(不滑動)Touchup,觸發順序:onDown→onSingleTapUp→onSingleTapConfirmed

單擊一下稍微慢一點的(不滑動) Touchup,觸發順序 :onDown→onShowPress→onSingleTapUp→onSingleTapConfirmed

• onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY):滑屏,用戶按下觸摸屏、快速移動後鬆開,由一個MotionEvent ACTION_DOWN、多個ACTION_MOVE、一個ACTION_UP觸發。
• onScroll(MotionEvent el, MotionEvent e2,float distanceX, float distanceY):在屏幕上拖動事件。無論是用手拖動View,還是以拋的動作滾動,都會多次觸發這個函數,在ACTION_MOVE動作發生時就會觸發該函數。

滑屏,即手指觸動屏幕後,稍微滑動後立即鬆開,觸發順序:onDown→onScroll→onScroll→onScroll→...→onFling

拖動,觸發順序:onDown→onScroll→onScroll→onFling

可見,無論是滑屏還是拖動,影響的只是中間onScroll被觸發的數量而己,最終都會觸發onFling事件。

2.示例

要使用 GestureDetector,有四步要走 。
(1) 創建 OnGestureListener()監聽函數。

GestureDetector.OnGestureListener listener = new GestureDetector.OnGestureListener() {
};
或構造類:
private class gestureListener implements GestureDetector.OnGestureListener {
}

(2) 創建GestureDetector實例mGestureDetector。

GestureDetector gestureDetector=new GestureDetector(GestureDetector.OnGestureListener listener};
GestureDetector gestureDetector=new GestureDetector(Context context, GestureDetector.OnGestureListener listener};
GestureDetector gestureDetector=new GestureDetector(Context context, GestureDetector.SimpleOnGestureListener listener);

(3) 在onTouch(View v, MotionEvent event)中進行攔截。

public boolean onTouch(View v, MotionEvent event} {
    return mGestureDetector.onTouchEvent(event);
}

(4) 綁定控件。

TextView tv = (TextView) findViewByid(R.id.tv);
tv.setOnTouchListener(this);

示例:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	tools:context="com.example.gesturedetectorinterface.MainActivity">
	<TextView
		android:id="@+id/tv"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:layoutmargin="50dip"
		android:background="#ff00ff"
		android:text="@string/hello_world"/>
</RelativeLayout>
public class MainActivity extends Activity implements View.OnTouchListener {
    private GestureDetector mGestureDetector;

    @Override
    protected void onCreate(Bundle savedinstanceState) {
        super.onCreate(savedinstanceState);
        setContentView(R.layout.activity_main);

        TextView tv = (TextView) findViewById(R.id.tv);
        tv.setOnTouchListener(this);
        tv.setFocusable(true);
        tv.setClickable(true);
        tv.setLongClickable(true);

        mGestureDetector = new GestureDetector(new MyGestureListener());
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return mGestureDetector.onTouchEvent(event);
    }

    private class MyGestureListener implements GestureDetector.OnGestureListener {
        @Override
        public boolean onDown(MotionEvent e) {
            Log.d("TAG", "onDown");
            return false;
        }

        @Override
        public void onShowPress(MotionEvent e) {
            Log.d("TAG", "onShowPress");
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            Log.d("TAG", "onSingleTapUp");
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent el, MotionEvent e2, float distanceX, float distanceY) {
            Log.d("TAG2", "onScroll:" + (e2.getX() - el.getX()) + "" + distanceX);
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            Log.d("TAG", "onLongPress");
        }

        @Override
        public boolean onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY) {
            Log.d("TAG", "onFling");
            return true;
        }
    }
}

GestureDetector.OnDoubleTapListener接口:

1.構建

方法一:新建一個類,同時派生OnGestureListener和OnDoubleTapListener

private class gestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
}

方法二:使用GestureDetector.setOnDoubleTapListener()函數設置雙擊監聽

// 構建 GestureDetector 實例
mGestureDetector = new GestureDetector(new MyGestureListener());
private class MyGestureListener implements GestureDetector.OnGestureListener {
}
// 設置雙擊監聽
mGestureDetector.setOnDoubleTapListener(new MyDoubleTapListener());
private class MyDoubleTapListener implements GestureDetector.OnDoubleTapListener {
}

可以看到,無論是在方法一還是在方法二中,都需要派生自GestureDetector.OnGestureListener。

2.函數講解

先來看一下OnDoubleTapListener接口必須重寫的三個函數。

private class doubleTapListener implements GestureDetector.OnDoubleTapListener {

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        // TODO Auto-generated method stub
        return false;
    }
}

• onSingleTapConfirmed(MotionEvente):單擊事件,用來判定該次單擊是SingleTap,而不是DoubleTap。如果連續單擊兩次,就是DoubleTap手勢;如果只單擊一次,系統等待一段時間後沒有收到第二次單擊,則判定該次單擊爲SingleTap,而不是DoubleTap,然後觸發SingleTapConfinned事件。觸發順序:onDown→onSingleTapUp→onSingleTapConfinned。OnGestureListener有這樣一個函數onSingleTapUp(),它和onSingleTapConfinned()函數容易混淆。二者的區別是:對於onSingleTapUp()函數來說,只要手擡起就會被觸發;而對於onSingleTapConfinned0函數來說,如果雙擊,則該函數就不會被觸發。
• onDoubleTap(MotionEvente):雙擊事件。
• onDoubleTapEvent(MotionEvente):雙擊間隔中發生的動作。指在觸發onDoubleTap以後,在雙擊之間發生的其他動作,包含down、up和move事件。

GestureDetector.SimpleOnGestureListener類:

SimpleOnGestureListener類與OnG巳stureListener和OnDoubleTapListener接口的不同之處在於:
(1)這是一個類,在它的基礎上新建類,要用巳xtends派生,而不能用implements繼承。
(2)OnGestureListener和OnDoubleTapListen巳r接口裏的函數都是被強制重寫的,即使用不到也要重寫出來一個空函數:而在SimpleOnGestureListener類的實例或派生類中不必如此,可以根據情況,用到哪個函數就重寫哪個函數,因爲SimpleOnGestureListener類本身已經實現了這兩個接口中的所有函數,只是裏面全是空的而已。

SimpleOnGestureListener類內部的函數與OnGestureListener和OnDoubleTapListener接口中的函數是完全相同的。唯一不同的就是 SimpleOnGestureListener 類內部的函數不必被強制全部重寫,用到哪個函數就重寫哪個函 數;而OnGestureListener和OnDoubleTapListener是接口,它們內部的函數是必須被重寫的。

onFling()函數的應用——識別是向左滑還是向右滑

boolean onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY)
• el:第一個ACTION_DOWN MotionEvent
• e2:最後一個ACTION_MOVE MotionEvent
• velocityX:X軸上的移動速度,單位爲像素/秒
• velocityY:Y軸上的移動速度,單位爲像素/秒

實現的功能:當用戶向左滑動距離超過100像素,且滑動速度超過100像素/秒時,即判斷爲向左滑動:向右同理。核心代碼是在onFling()函數中判斷當前的滑動方向及滑動速度是不是達到指定值。代碼如下:

private class simpleGestureListener extends GestureDetector.SimpleOnGestureListener {
    /* ******OnGestureListener的函數****** */
    final int FLING_MIN_DISTANCE = 100, FLING_MIN_VELOCITY = 200;
    // 觸發條件:
    // X軸的座標位移大於FLING_MIN_DISTANCE,且移動速度大於FLING_MIN_VELOCITY像素/秒
    public boolean onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY) {
        if (el.getX() - e2.getX() > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {// 向左滑
            Log.d("TAG", "Fling left");
        } else if (e2.getX() - el.getX () > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {// 向右滑
            Log.i("TAG", "Fling right" );
        }
        return true;
    }
}

二、Window與WindowManager

Window表示窗口,在某些特殊的時候,比如需要在桌面或者鎖屏上顯示一些類似懸浮窗的效果,就需要用到Window。Android中所有的視圖都是通過Window來呈現的,不管是Activity、Dialog還是Toast,它們的視圖實際上都是附加在Window上的。而WindowManager則提供了對這些Window的統一管理功能。

Window與WindowManager的聯繫:

使用WindowManager來添加一個Window:

WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(width, height, type, flags, format);
manager.addView(btn, layoutParams);
◆ flags:
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
表示此Window不需要獲取焦點,不接收各種輸入事件,此標記會同時啓用FLAG_NOT_TOUCH_MODAL,
最終事件會直接傳遞給下層具有焦點的Window。
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
自己Window區域內的事件自己處理;自己Window區域外的事件傳遞給底層Window處理。
一般這個選項會默認開啓,否則其他Window無法接收事件。
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
可以讓此Window顯示在鎖屏上。

◆ type:
type參數也是int類型的,表示Window的類型。
Window有三種類型:應用Window、子Window、系統Window。
應用Window:對應着一個Activity。
子Window:不能獨立存在,它需要附屬在特定的父Window中,比如Dialog就是一個子Window。
系統Window:需要聲明權限才能創建,比如Toast和系統狀態欄都是系統Window。
Window是分層的,層級大的Window會覆蓋在層級小的Window上面。
● 應用Window層級範圍:1~99
● 子Window層級範圍:1000~1999
● 系統Window層級範圍:2000~2999
type參數就對應以上這些數字。如果想讓Window置於頂層,則採用較大的層級即可。

如果是系統類型的Window,則需要在AndroidManifest.xml中配置如下權限聲明:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

WindowManager提供的功能很簡單,常用的只有三個方法,即添加View、更新View和刪除View。這三個方法定義在ViewManager中,而 WindowManager繼承自ViewManager。

public interface WindowManager extends ViewManager {
}
public interface ViewManager {
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

WindowManager操作Window的過程更像在操作Window中的View。

示例:騰訊手機管家懸浮窗的小火箭效果

<?xml version ="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:app="http://schemas.android.com/apk/res-auto"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match parent">
	<Button
		android:id="@+id/add_btn"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="add view"/>
	<Button
		android:id="@+id/rmv_btn"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="remove view"/>
</LinearLayout>
public class MainActivity extends Activity implements View.OnTouchListener, View.OnClickListener {
    private Button mCreateWndBtn, mRmvWndBtn;
    private ImageView mImageView;
    private WindowManager.LayoutParams mLayoutParams;
    private WindowManager mWindowManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            Intent myIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            startActivityForResult(myIntent, 100);
        } else {
            initView();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 100) {
            initView();
        }
    }

    private void initView() {
        mCreateWndBtn = (Button) findViewById(R.id.add_btn);
        mRmvWndBtn = (Button) findViewById(R.id.rmv_btn);
        mCreateWndBtn.setOnClickListener(this);
        mRmvWndBtn.setOnClickListener(this);

        mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE: {
                mLayoutParams.x = rawX;
                mLayoutParams.y = rawY;
                mWindowManager.updateViewLayout(mImageView, mLayoutParams);
                break;
            }
        }
        return false;// return false:會執行onClick方法
    }

    @Override
    protected void onDestroy() {
        try {
            mWindowManager.removeView(mImageView);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        super.onDestroy();
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.add_btn) {
            mImageView = new ImageView(this);
            mImageView.setBackgroundResource(R.mipmap.ic_launcher);

            mLayoutParams = new WindowManager.LayoutParams(
                    WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 2099,
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    ,
                    PixelFormat.TRANSPARENT);
            mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
            mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
            mLayoutParams.x = 0;
            mLayoutParams.y = 300;
            mImageView.setOnTouchListener(this);
            mWindowManager.addView(mImageView, mLayoutParams);
        } else if (v.getId() == R.id.rmv_btn) {
            mWindowManager.removeViewImmediate(mImageView);
        }
    }
}
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

需要注意的是,在 SDK API>=23(Android6.0)時,不僅需要在AndroidManifest.xml中添加權限申請,也需要在代碼中動態申請。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章