1. 程式人生 > >Android自定義View總結(一)基礎知識與例項

Android自定義View總結(一)基礎知識與例項

自定義View是最能體現一個Android開發者水平的技能之一了。

接下來的一些列部落格將總結一下Android的自定義相關View知識,包括View的結構,事件體系,工作原理,自定義View的繪製等。

參考資料部分來自於書上以及各種部落格。

新建了一個qq群 482543750,歡迎一起學習Android的小夥伴加入。

提供各種Android學習資料,面試資料,Android簡歷模板。

一、概述 Android中,View不屬於四大元件,但它甚至比Receiver和Provider都要重要。 Android提供了許多基礎的控制元件,但遠遠不能滿足我們的需要,很多時候我們根據需求進行新控制元件的定義,這就需要我們對View體系有深入理解。 二、基礎知識
1、View是什麼 View是Android所有控制元件的基類,簡單到TextView、Button,複雜到RelativeLayout,LinearLayout,其共同基類都是View。 所以,View可以理解為控制元件的抽象,也是一個控制元件。 除此之外,還有ViewGroup,字面意義上,它表示控制元件組,內部可以包含許多個控制元件。 ViewGroup也繼承自View,這意味著,一個View的可以是單個控制元件,也可以是多個控制元件組成的一組控制元件,這就形成了View樹。 下面這個圖很好地體現了View的繼承關係
2、View的相關引數 View的位置決定於它的四個頂點,對應View的四個屬性: Top:左上角縱座標,通過getTop ()獲得 Left:左上角橫座標,通過getLeft()獲得 Right: 右下角橫座標,通過getRight ()獲得 Bottom: 右下角縱座標,通過getBottom ()獲得 這些座標都是相對於View的父容器所說的,是一種相對座標。 下面這張圖表示的是View中涉及位置引數的各個方法對應的具體含義。 最外層是手機螢幕,中間是一個ViewGroup巢狀一個View。 涉及到的其他方法請繼續往下看。

此外,引數x,y表示View左上角的橫縱座標, translationX和translationY表示View的左上角相對於父容器的偏移量。 他們都有相應的Get/Set方法 這幾個引數也是相對於父容器的座標 可以知道,這幾個引數換算關係如下 x = left + translationX y = top + translationY 利用這些引數,我們來自定義一個能隨手指滑動而改變位置的View 實現如下效果: 初始位置:

手指滑動後,自定義View走到了圖示位置:


程式碼如下: 自定義View
public class DragView extends View{

    int lastX;
    int lastY;

    public DragView(Context context) {
        super(context);
    }

    public DragView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int x = (int) event.getX();
        int y = (int) event.getY();
        Log.e("觸發onTouchEvent",x+"::::::"+y);

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:{
                lastX = x;
                lastY = y;
            }
            break;
            case MotionEvent.ACTION_MOVE:{
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                Log.e("觸發ACTION_MOVE",offsetX+"::::::"+offsetY);
                layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
                Log.d("DragView",getLeft()+"______"+getTop()+"-------"+getBottom()+"-------"+getRight());
            }
            break;
        }

        return true;
    }
}

佈局:
<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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.lian.scrolltest.MainActivity">

    <com.lian.scrolltest.DragView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#000000"

        />
</RelativeLayout>

MainActivity直接顯示佈局即可 很好地實現瞭如上效果 3、MotionEvent和TouchSlop (1)MotionEvent 我們在自定義View的時候,常常需要在onTouchEvent()中定義觸控行為 典型的觸控事件型別包括: ACTION_DOWN:手指剛剛接觸螢幕 ACTION_MOVE:手指在螢幕上移動 ACTION_UP:手指從螢幕離開 通過MotionEvent物件,我們可以得到點選事件的一系列位置引數 getX(),getY():觸控事件發生的位置相對於View的座標 getRawX(),getRawY()返回返回相對於螢幕左上角的x 和 y 座標 (2)TouchSlop 表示系統能辨識出的認為是滑動的最小距離。 若兩次滑動小於此常量,判定為不屬於滑動操作 這個常量的大小和手機有關。 通過如下方式獲得:
ViewConfiguration.get(getContext()).getScaledTouchSlop();

我們處理滑動事件時,可以用這個引數進行過濾。 4、VelocityTracker、GestureDetector、Scroller (1)VelocityTracker 用於追蹤滑動過程中的速度,包括水平和豎直方向的速度。 使用方法: 在View的onTouchEvent()中將Event託管給VelocityTracker, 採用相應API獲取引數
//獲取物件
VelocityTracker velocityTracker = VelocityTracker.obtain();
//託管event
velocityTracker.addMovement(event);
//設定時間間隔,結果會表示為每1000毫秒經過多少畫素,若設定為100,結果表示為沒100毫秒經過多少畫素
velocityTracker.computeCurrentVelocity(1000);
//獲取X和Y方向上的速度
int xVelicity = (int) velocityTracker.getXVelocity();
int yVelicity = (int) velocityTracker.getYVelocity();
比如1秒內X方向滑動了100畫素,那麼引數設定為1000時,結果就為100,表示1000毫秒劃過100畫素 引數設定為100時,結果就為10(表示每100毫秒劃過10畫素) 不需要使用時,對其進行回收
velocityTracker.clear();
velocityTracker.recycle();

(2)GestureDetector GestureDetector中將封裝了一系列觸控行為,包括單擊、滑動、長按,雙擊等。 使用: 在自定義View中實現onGestureDetector介面,在其中重寫onSingleTapTop()-單擊事件、onFiling()-快速滑動、omLongPress()-長按、onDoubleTap()-雙擊 等方法,定義自己的事件處理邏輯 還有,在onTouchEvent()中:
//獲取物件
GestureDetector gestureDetector = new GestureDetector(this);
//解決長按屏幕後無法拖動的問題
gestureDetector.setIsLongpressEnabled(false);
//託管event
boolean consume = gestureDetector.onTouchEvent(event);
return consume;

(3)Scroller 用於實現View的彈性滑動。 為了讓View實現滑動,我們常常使用scrollTo和ScrollBy,但其過程是瞬間完成的,沒有過度效果,使用者體驗並不好。 使用Scroller和View的computeScroll配合,可以實現有過渡效果的滑動 三、View的滑動 滑動是自定義View使用最多的效果之一, 有三種實現方式: a、View的scrollTo/ScrollBy方法 b、使用動畫為View施加平移效果 c、改變View的LayoutParams是的View重新佈局實現滑動 1、使用scrollTo/ScrollBy 原始碼:
/**
 * Set the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the x position to scroll to
 * @param y the y position to scroll to
 */
public void scrollTo(int x, int y) {
    if (mScrollX != x || mScrollY != y) {
        int oldX = mScrollX;
        int oldY = mScrollY;
        mScrollX = x;
        mScrollY = y;
        invalidateParentCaches();
        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
        if (!awakenScrollBars()) {
            postInvalidateOnAnimation();
        }
    }
}

/**
 * Move the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the amount of pixels to scroll by horizontally
 * @param y the amount of pixels to scroll by vertically
 */
public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

scrollBy實際上也呼叫scrollTo方法,實現基於當前位置的滑動,scrollTo實現基於所傳遞引數的絕對滑動 mScrollX和mScrollY可以動過get方法得到。 mScrollX的值總是等於View的左邊緣到View的內容左邊緣水平方向的距離, mScrollY的值總是等於View的上邊緣和View的內容上邊緣豎直方向的距離。 需要注意的是,scrollBy和scrollTo只能改變View的內容的位置而不能改變View在佈局中的位置 在View的內容位置改變是,mScrollX和mScrollY值可正可負 2、使用動畫 通過動畫可以讓一個View進行平移,主要是操作View的translationX和translationY屬性,可以用View動畫,也可以用屬性動畫。 以屬性動畫為例, 我們在上面例子的基礎上新增一個按鈕

點選按鈕,View在1秒鐘的時間內向右平移200畫素


通過如下程式碼:

Button button = (Button) findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        dragView.animate().translationX(200).setDuration(1000).start();
    }
});

3.改變佈局引數 即改變LayoutParams, 比如我們讓以上自定義View向右平移100畫素 只要將此View的marginLeft引數值增加100px 同樣以上為例,將自定義View的寬度增加100px,向右平移100px
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) dragView.getLayoutParams();
        layoutParams.width+=100;
        layoutParams.leftMargin+=100;
        dragView.requestLayout();
        //或者dragView.setLayoutParams(layoutParams);
    }
});

點選按鈕發現View向右滑動而且變胖了,但是瞬間滑動過去的,沒有動畫效果