注:本系列屬於學習筆記,學習內容主要來自於劉望舒的博客,特此聲明
1.視圖座標系
Android中所有控件都是繼承View類的,下圖可以看到其中的關係
2.Android座標系
Android中有兩種座標系:Android座標系、視圖座標系
2.1 Android座標系
在Android中,將屏幕的左上角的頂點作爲Android座標系的原點,這個原點向右是X軸正方向,原點向下是Y軸正方向
在Android中,MotionEvent中提供的getRawX()和getRawY()獲取的座標都是Android座標系的座標
2.2 視圖座標系
View獲取自身高度
- getHeight():獲取View自身高度
- getWidth():獲取View自身寬度
View自身座標
通過如下方法可以獲得View到其父控件(ViewGroup)的距離:
- getTop():獲取View自身頂邊到其父佈局頂邊的距離
- getLeft()、getRight():獲取View自身左邊到其父佈局左邊的距離(或右邊)
- getBottom():獲取View自身底邊到其父佈局底邊的距離
MotionEvent提供的方法
我們看上圖那個深藍色的點,假設就是我們觸摸的點,我們知道無論是View還是ViewGroup,最終的點擊事件都會由onTouchEvent(MotionEvent event)方法來處理,MotionEvent也提供了各種獲取焦點座標的方法:
- getX()、getY():獲取點擊事件距離控件左邊的距離,即視圖座標
- getRawX()、getRawY():獲取點擊事件距離整個屏幕左邊距離,即絕對座標
二、實現View滑動的六種方法
其實不管是那種滑動的方式基本思想都是類似的:當觸摸事件傳到View時,系統記下觸摸點的座標,手指移動時系統記下移動後的觸摸的座標並算出偏移量,並通過偏移量來修改View的座標
1. Layout()
view進行繪製的時候會調用onLayout()方法來設置顯示的位置,因此我們同樣也可以通過修改View的left、top、right、bottom這四種屬性來控制View的座標
Step 1: 自定義一個View,在onTouchEvent()方法中獲取觸摸點的座標
public boolean onTouchEvent(MotionEvent event){
// 獲取到手指處的橫縱座標
int x = (int)event.getX();
int y = (int)event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = Y;
break;
}
...
}
Step 2: 在ACTION_MOVE事件中計算偏移量,再調用layout()方法重新放置這個自定義View的位置
case MotionEvent.ACTION_MOVE:
//計算移動的距離
int offsetX = x - lastX;
int offsetY = y - lastY;
//調用layout方法來重新放置它的位置
layout(getLeft()+offsetX, getTop()+offsetY, getRight()+offsetX , getBottom()+offsetY);
break;
Step Example(1) : 自定義View的全部代碼
package zhongshijie.viewsystem.CustomView;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* 可以跟隨手指拖動的自定義View
* Created by ZhongShijie on 2017/8/7.
*/
public class MoveTouchView extends View {
private int lastX;
private int lastY;
public MoveTouchView(Context context, AttributeSet attributeSet, int defStyleAttr){
super(context, attributeSet, defStyleAttr);
}
public MoveTouchView(Context context,AttributeSet attributeSet){
super(context, attributeSet);
}
public MoveTouchView(Context context){
super(context);
}
public boolean onTouchEvent(MotionEvent event){
//獲取到手指處的橫座標和縱座標
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
//計算移動的距離
int offsetX = x - lastX;
int offsetY = y - lastY;
//調用layout方法來重新放置它的位置
layout(getLeft()+offsetX, getTop()+offsetY,
getRight()+offsetX , getBottom()+offsetY);
break;
}
return true;
}
}
Step Example(2) : 佈局中引用自定義View
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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="zhongshijie.viewsystem.MainActivity">
<zhongshijie.viewsystem.CustomView.MoveTouchView
android:layout_width="80dp"
android:layout_height="80dp"
android:background="@color/colorAccent"/>
</android.support.constraint.ConstraintLayout>
2. offsetLeftAndRight() 與 offsetTopAndBottom()
這兩種方法和layout()方法效果方法差不多,使用也差不多,我們將ACTION_MOVE中的代碼替換成如下代碼:
case MotionEvent.ACTION_MOVE:
//計算移動的距離
int offsetX = x - lastX;
int offsetY = y - lastY;
//對left和right進行偏移
offsetLeftAndRight(offsetX);
//對top和bottom進行偏移
offsetTopAndBottom(offsetY);
break;
根據這兩種方法,我們可以完成:跟隨手指拖動而移動的View只進行單一左右移動或者上下移動
3. LayoutParams(改變佈局參數)
LayoutParams主要保存了一個View的佈局參數,因此我們可以通過LayoutParams來改變View的佈局的參數從而達到了改變View的位置的效果。同樣的我們將ACTION_MOVE中的代碼替換成如下代碼:
case MotionEvent.ACTION_MOVE:
//計算移動的距離
int offsetX = x - lastX;
int offsetY = y - lastY;
//改變佈局參數
LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
此處的LinearLayout是該自定義View的父控件,如果你的自定義View的父控件是RelativeLayout,則需要改爲RelativeLayout
注:同時,除了使用LayoutParams外,還可以用ViewGroup.MarginLayoutParams來實現,將上面的LinearLayout.LayoutParams替換爲ViewGroup.MarginLayoutParams即可實現。
4. 動畫
- 可以採用View動畫來移動
Step 1 : 在res目錄新建anim文件夾並創建translate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="300" android:duration="1000"/>
</set>
Step 2 : 在Java代碼中引用
mCustomView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate)
- 當然也可以採用屬性動畫
Step 1 : 在Activity中獲取到控件mCustomView後,使用屬性動畫
ObjectAnimator.ofFloat(mCustomView,"translationX",0,300).setDuration(1000).start();
5. scollTo與scollBy
scollTo(x,y)表示移動到一個具體的座標點,而scollBy(dx,dy)則表示移動的增量爲dx、dy
其實,scollBy最終也是要調用scollTo的。另外,這兩個方法都是針對View的內容的,如果在ViewGroup中使用則是移動他的子View
Step 1 : 將ACTION_MOVE中的代碼替換成如下代碼
// 實現CustomView隨着我們手指移動的效果,需要將偏移量設置爲負值
((View)getParent()).scrollBy(-offsetX,-offsetY);
6. Scroller
可以使用Scroller來實現過度效果的滑動。但是,Scroller本身是不能實現View的滑動的,需要配合View的滑動,它需要配合view的computeScroll()方法才能彈性滑動的效果。
Step 1 :在自定義View的構造函數中,初始化Scroller
public MoveTouchView(Context context,AttributeSet attributeSet){
super(context, attributeSet);
mScroller = new Scroller(context);
}
Step 2 : 重寫自定義view中的computeScroll()方法
該方法中我們調用父類的scrollTo()方法,通過Scroller來不斷獲取當前的滾動值,每滑動一小段距離我們就調用invalidate()方法不斷的進行重繪,重繪就會調用computeScroll()方法,這樣我們就通過不斷的移動一個小的距離並連貫起來就實現了平滑移動的效果
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
//通過不斷的重繪不斷的調用computeScroll方法
invalidate();
}
}
Step 3 : 在CustomView中寫一個smoothScrollTo()方法,調用Scroller.startScroll()方法,在2000毫秒內沿X軸平移delta像素
public void smoothScrollTo(int destX,int destY){
int scrollX=getScrollX();
int delta=destX-scrollX;
//1000秒內滑向destX
mScroller.startScroll(scrollX,0,delta,0,2000);
invalidate();
}
Step 4 : 在ViewSlideActivity.java中調用CustomView的smoothScrollTo()方法:
//使用Scroll來進行平滑移動
mCustomView.smoothScrollTo(-400,0);