Android群英傳筆記第三章(四)

自定義ViewGroup

在學自定義ViewGroup的時候對書中的知識有部分沒有掌握,所以跳到了本書的第五章進行學習了相關的內容
以下為第五章筆記(學完這章會把第三章的筆記補上)

5.1 滑動效果是如何產(chǎn)生的

滑動一個View本質上就是移動一個View,通過不斷改變View的坐標來實現(xiàn)這個效果锡宋;要實現(xiàn)View的滑動就要監(jiān)聽用戶觸摸事件某抓,并根據(jù)傳入的坐標迄损,動態(tài)不斷的改變View的坐標坑质,從而實現(xiàn)滑動
#######5.1.1Android坐標系
在Android中將屏幕的左上角的頂點作為Android坐標系的原點彰檬,這個點向右是X軸正方形棋返,向下是y軸正方向
系統(tǒng)通過getLocationOnScreen(location)方法(PS:int [] location = new int[2]夺荒,獲取后location[0]代表x坐標location[1]代表y坐標)來獲取Android坐標系中點的坐標

5.1.2 視圖坐標系

描述的是子視圖在父視圖中的位置關系役首,以父視圖的左上角為坐標原點
####### 5.1.3觸摸事件-MotionEvent
在MotionEvent中封裝了一些常用的事件常量
單點觸摸按下動作

  • public static final int ACTION_DOWN = 0;
    單點觸摸離開動作
  • public static final int ACTION_UP = 1;
    觸摸點移動動作
  • public static final int ACTION_MOVE = 2;
    觸摸動作取消
  • public static final int ACTION_CANCEL = 3;
    觸摸動作超出邊界
  • publicstatic final int ACTION_OUTSIDE = 4;
    多點觸摸按下動作
  • public static final int ACTION_POINTER_DOWN = 5;
    多點離開動作
  • public static final int ACTION_POINTER_UP = 6;
    通常我們會在onTouchEvent(MotionEvent event)方法中通過event.getAction()來獲取觸控事件的類型备禀,并使用switch-case方法來進行篩選
    使用的模板
@Override
    public boolean onTouchEvent(MotionEvent event) {
        //獲取當前輸入的的X,Y坐標(視圖坐標)
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN :
                //處理輸入的按下事件
                break;
            case MotionEvent.ACTION_HOVER_MOVE:
                //處理輸入的移動事件
                break;
            case MotionEvent.ACTION_UP:
                //處理輸入的離開事件
                break;
        }
        return true;
    }

坐標系總結


Android坐標系

getLeft():View自身的左邊到父布局左邊的距離
getRight():View自身的右邊到父布局左邊的距離
getBottom():View自身的底邊到父布局頂邊的距離
getTop():View自身的頂邊到父布局頂邊的距離

getX():觸摸點到控件左邊的距離(相對距坐標)
getY():觸摸點到控件頂邊的距離(相對坐標)
getRawX():觸摸點到屏幕左邊的距離(絕對坐標)
getRawY():觸摸點到屏幕頂邊的距離(絕對坐標)

5.2實現(xiàn)滑動的七中方法

原理:當觸摸View的時候氏捞,系統(tǒng)記下當前觸摸點的坐標吃沪,當手指移動時翠储,系統(tǒng)記下移動后觸摸點的坐標爆哑,兩者之差獲取到偏移量洞难,并通過偏移量來修改View的坐標,不斷的重復揭朝,從而實現(xiàn)滑動

5.2.1layout方法
package com.example.phonejason;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by 小新 on 2016/6/9.
 */
public class MyRec extends View {
    private int lastX,lastY;
    private Paint mPaint = null;
    public MyRec(Context context) {
        super(context);
        mPaint = new Paint();
    }

    public MyRec(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
    }

    public MyRec(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.RED);
        //畫一個矩形
        canvas.drawRect(0,0,100,100,mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            //第一次觸摸按下的時候獲取到的x,y賦值給lastX队贱,lastY
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            //觸摸移動時x,y會改變,和第一次觸摸的lastX潭袱,lastY相減算出偏移
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                //每次移動都會調用latout()方法對自己重新布局柱嫌,從而達到移動View的效果
                layout(getLeft()+offsetX,getTop()+offsetY,getRight()+offsetX,getBottom()+offsetY);
                break;
            default:
                break;
        }
        return true;
    }
}

也可以使用絕對絕對坐標來計算偏移量,但是和相對坐標有一定的區(qū)別(使用getRawX()和getRawY()是當前View的左邊的距離距屏幕左邊的距離+getX()屯换,這個值隨著View的滑動一直是變化的编丘;但是getX是觸摸點距離控件左邊的距離,因為控件也是滑動的所以getX()是一直不變化的彤悔,所以在每次執(zhí)行完ACTION_MOVE時我們重新設置初始坐標嘉抓,這樣才能準確獲取偏移量)

package com.example.phonejason;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by 小新 on 2016/6/9.
 */
public class MyRec extends View {
    private int lastX,lastY;
    private Paint mPaint = null;
    public MyRec(Context context) {
        super(context);
        mPaint = new Paint();
    }

    public MyRec(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
    }

    public MyRec(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.RED);
        //畫一個矩形
        canvas.drawRect(0,0,100,100,mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction()){
            //第一次觸摸按下的時候獲取到的x,y賦值給lastX,lastY
            case MotionEvent.ACTION_DOWN:
                lastX = rawX;
                lastY = rawY;
                break;
            //觸摸移動時x,y會改變晕窑,和第一次觸摸的lastX抑片,lastY相減算出偏移
            case MotionEvent.ACTION_MOVE:
                int offsetX = rawX - lastX;
                int offsetY = rawY - lastY;
                //每次移動都會調用latout()方法對自己重新布局,從而達到移動View的效果
                layout(getLeft()+offsetX,getTop()+offsetY,getRight()+offsetX,getBottom()+offsetY);
                lastX = rawX;
                lastY = rawY;
                break;
            default:
                break;
        }
        return true;
    }
}
5.2.2 offsetLeafAndRight()與offsetTopAndBottom()

這個方法是系統(tǒng)提供的一個對左右杨赤、上下移動API的封裝敞斋,使用如下代碼可以完成View的重新布局级遭,效果與Layout一樣,代碼如下

offsetLeftAndRight(offsetX);
 offsetTopAndBottom(offsetY);
5.2.3 LayoutParams

使用getLayoutParams()來獲取一個View的LayoutParams渺尘,LayoutParams保存一個View的布局參數(shù)

 LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft()+offsetX;
layoutParams.topMargin = getTop()+offsetY;
setLayoutParams(layoutParams);

注意:獲取LayoutParams時需要根據(jù)View所在父布局的類型設置不同的類型挫鸽;使用ViewGroup.MarginLayoutParams就不需要考慮父布局了,但是本質上是一樣的

 ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
                marginLayoutParams.leftMargin = getLeft()+offsetX;
                marginLayoutParams.topMargin = getTop()+offsetY;
                setLayoutParams(marginLayoutParams);
5.2.4 scrollTo與scrollBy()

scrollBy(x,y)表示移動到一個具體的坐標(x,y)鸥跟;
scrollTo(dx,dy)表示移動的增量為dx,dy丢郊;
scrollBy(x,y)和scrollTo(dx,dy)是讓View的內容移動,例如TextView的內容就是文本医咨;ImageView的內容就是drawable枫匾;如果在ViewGroup中使用,那么移動的是所有的View拟淮;所以我們要移動圖形的話我們需要獲取它的父布局然后移動(這里要注意一點很重要的是:父布局往右移動20px干茉,字布局是向左移動20px,實際上我們想子布局向右移動20px那么我們就需要在父布局中往右移動-20px很泊;選的參考系不一樣)

package com.example.phonejason;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

/**
 * Created by 小新 on 2016/6/9.
 */
public class MyRec extends View {
    private int lastX,lastY;
    private Paint mPaint = null;
    public MyRec(Context context) {
        super(context);
        mPaint = new Paint();
    }

    public MyRec(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
    }

    public MyRec(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.RED);
        //畫一個矩形
        canvas.drawRect(0,0,100,100,mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            //第一次觸摸按下的時候獲取到的x,y賦值給lastX角虫,lastY
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            //觸摸移動時x,y會改變,和第一次觸摸的lastX委造,lastY相減算出偏移
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                //注意這里要取負數(shù)
                ((View) getParent()).scrollBy(-offsetX, -offsetY);
                break;
            default:
                break;
        }
        return true;
    }
}

學到這里我們就可以學回

自定義ViewGroup

ViewGroup的存在就是為了對其子View進行管理戳鹅,為其子View添加顯示、響應規(guī)則昏兆。自定義ViewGroup需要重寫onMeasure()對其子View進行測量枫虏,onLayout()方法來確定子View的位置,onTouchEvent()增加響應事件
Demo實現(xiàn)一個類似ScrollView:
效果:上下滑動的功能爬虱,當一個子View向上滑動大于一定的距離后隶债,松開手指,它將自動向上滑動跑筝,顯示下一個子View死讹。同理向下也是一樣的道理

package com.example.mygroup;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Scroller;

/**
 * Created by 小新 on 2016/6/13.
 */
public class mygroup extends ViewGroup {

    private int mScreenHeight;
    private Scroller mScroller;
    private int mLastY;
    private int mStart;
    private int mEnd;

    public mygroup(Context context) {
        super(context);
        initView(context);
    }

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

    public mygroup(Context context, AttributeSet attrs,
                        int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    private void initView(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(
                Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        mScreenHeight = dm.heightPixels;
        mScroller = new Scroller(context);
    }

    @Override
    protected void onLayout(boolean changed,
                            int l, int t, int r, int b) {
        int childCount = getChildCount();
        // 璁劇疆ViewGroup鐨勯珮搴?
        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
        mlp.height = mScreenHeight * childCount;
        setLayoutParams(mlp);
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                child.layout(l, i * mScreenHeight,
                        r, (i + 1) * mScreenHeight);
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec,
                             int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count = getChildCount();
        for (int i = 0; i < count; ++i) {
            View childView = getChildAt(i);
            measureChild(childView,
                    widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastY = y;
                mStart = getScrollY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                int dy = mLastY - y;
                if (getScrollY() < 0) {
                    dy = 0;
                }
                if (getScrollY() > getHeight() - mScreenHeight) {
                    dy = 0;
                }
                scrollBy(0, dy);
                mLastY = y;
                break;
            case MotionEvent.ACTION_UP:
                int dScrollY = checkAlignment();
                if (dScrollY > 0) {
                    if (dScrollY < mScreenHeight / 3) {
                        mScroller.startScroll(
                                0, getScrollY(),
                                0, -dScrollY);
                    } else {
                        mScroller.startScroll(
                                0, getScrollY(),
                                0, mScreenHeight - dScrollY);
                    }
                } else {
                    if (-dScrollY < mScreenHeight / 3) {
                        mScroller.startScroll(
                                0, getScrollY(),
                                0, -dScrollY);
                    } else {
                        mScroller.startScroll(
                                0, getScrollY(),
                                0, -mScreenHeight - dScrollY);
                    }
                }
                break;
        }
        postInvalidate();
        return true;
    }
    private int checkAlignment() {
        int mEnd = getScrollY();
        //mEnd和mStart是在父布局中的位置,所以當
        //最后一次的值減去第一次的值>0的話向上
        boolean isUp = ((mEnd - mStart) > 0) ? true : false;
        int lastPrev = mEnd % mScreenHeight;
        int lastNext = mScreenHeight - lastPrev;
        if (isUp) {
            return lastPrev;
        } else {
            return -lastNext;
        }
    }
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            scrollTo(0, mScroller.getCurrY());
            postInvalidate();
        }
    }

}

在布局中使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.example.mygroup.MainActivity">

    <com.example.mygroup.mygroup
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/test1" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/test2" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/test3" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/test4" />
    </com.example.mygroup.mygroup>
</LinearLayout>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末继蜡,一起剝皮案震驚了整個濱河市回俐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稀并,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件单默,死亡現(xiàn)場離奇詭異碘举,居然都是意外死亡,警方通過查閱死者的電腦和手機搁廓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門引颈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耕皮,“玉大人,你說我怎么就攤上這事蝙场×柰#” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵售滤,是天一觀的道長罚拟。 經(jīng)常有香客問我,道長完箩,這世上最難降的妖魔是什么赐俗? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮弊知,結果婚禮上阻逮,老公的妹妹穿的比我還像新娘。我一直安慰自己秩彤,他們只是感情好叔扼,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著漫雷,像睡著了一般币励。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上珊拼,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天食呻,我揣著相機與錄音,去河邊找鬼澎现。 笑死仅胞,一個胖子當著我的面吹牛,可吹牛的內容都是我干的剑辫。 我是一名探鬼主播干旧,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼妹蔽!你這毒婦竟也來了椎眯?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胳岂,失蹤者是張志新(化名)和其女友劉穎编整,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乳丰,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡掌测,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了产园。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汞斧。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡夜郁,死狀恐怖,靈堂內的尸體忽然破棺而出粘勒,到底是詐尸還是另有隱情竞端,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布庙睡,位于F島的核電站事富,受9級特大地震影響,放射性物質發(fā)生泄漏埃撵。R本人自食惡果不足惜赵颅,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望暂刘。 院中可真熱鬧饺谬,春花似錦、人聲如沸谣拣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽森缠。三九已至拔鹰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贵涵,已是汗流浹背列肢。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宾茂,地道東北人瓷马。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像跨晴,于是被迫代替她去往敵國和親欧聘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容