自定義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;
}
坐標系總結
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>