Android 自定義View:實現View的滑動效果


相關文章:

Android坐標系分析

Android自定義View 之 View的測量

Android 自定義View之View的繪制


之前學習了View的測量和繪制,我們已經可以定制自己喜歡外觀的View了

今天再來學習一下如何定制View的滑動效果


View的滑動效果,本質上就是通過改變View的坐標來實現的。

關於Android 坐標系,之前我也寫過文章特意講了,我們再簡單鞏固一下。

坐標系分兩種

一種是絕對坐標系,就是以手機屏幕左上角為原點

View.getLocationOnScreen(int[] location) 還有 MotionEvent.getRawX()MotionEvent.getRawY() 都是以這個坐標系為基准獲取的坐標

我們稱之為 絕對坐標

一種是視圖坐標系,就是以父視圖左上角為坐標原點

View.getLeft()等是以父布局為基准,

View.getLocationInWindow(int[] location)父窗體為基准,

Canvas繪制和MotionEvent.getX()等是以當前View(父視圖)為基准的


通常我們移動View,都和手指在屏幕上的觸碰,拖動離不開關系

那么,如何捕捉到手指的觸控呢?

這就要用到View的onTouchEvent(MotionEvent event)方法啦

public class MyView extends View{

public MyView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}

@Override
public boolean onTouchEvent(MotionEvent event){
return true;
}

}
onTouchEvent只有return true 這個事件的處理才會生效


而我們如何分辨傳來的MotionEvent是什么類型呢?

這就要用到MotionEvent封裝的事件常量,常用的有以下幾種

MotionEvent事件常量
事件常量 描述
ACTION_DOWN = 0 單點觸摸時按下動作
ACTION_UP = 1 單點觸摸時離開動作
ACTION_MOVE = 2 觸摸點移動動作
ACTION_CANCEL = 3 觸摸動作取消
ACTION_OUTSIDE = 4 觸摸動作超過邊界
ACTION_POINTER_DOWN = 5 多點觸摸按下動作
ACTION_POINTER_UP = 6 多點觸摸離開動作









通過這些事件常量,我們就可以定位到相應的事件,做相應的處理

通常我們onTouchEvent這個方法會以下面模式重寫

	@Override
public boolean onTouchEvent(MotionEvent event){
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//檢測到手指觸碰屏幕
//寫相關的事件
//比如,獲得觸點的x視圖坐標
int x = (int)event.getX();
break;
case MotionEvent.ACTION_MOVE:
//手指在屏幕上滑動
break;
case MotionEvent.ACTION_UP:
//手指離開屏幕
break;
}
return true;
}
當然,你也可以用if來判斷,只是架構不如switch清晰

此外,你也可以通過case分支檢測其他的觸摸事件並處理


實現滑動的七種方法


1.layout方法

View在進行繪制時,會調用onLayout()方法來設置當前顯示的位置,最終通過調用layout(left,top,right,bottom)設置View左上右下頂點的坐標來設置位置

(為什么四個點就可以?因為View本身是矩形)

下面的代碼,我們就通過這個方法的運用,來實現一個被手指拖動的小球。

public class MyView extends View{

//設置wrap_content時候View的大小
private int defaultWidth = 100;
private int defaultHeight = 100;
//畫筆 用於畫圓
private Paint p = new Paint();

//View當前的位置
private int rawX = 0;
private int rawY = 0;
//View之前的位置
private int lastX = 0;
private int lastY = 0;

public MyView(Context context){
super(context);
}
public MyView(Context context, AttributeSet set) {
super(context, set);
}

//畫一個紅色,圓心為View中心點,半徑為View寬度的圓
public void onDraw(Canvas canvas){
//Log.e("onDraw執行","true");
p.setColor(Color.RED);
int x = this.getLeft() + this.getWidth()/2;
int y = this.getTop() + this.getHeight()/2;
canvas.drawCircle(this.getWidth()/2, this.getHeight()/2, this.getWidth()/2, p);
}

//注意,觸摸事件的響應范圍僅限於該View的區域
public boolean onTouchEvent(MotionEvent event){
//Log.e("onTouchEvent執行","true");
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//Log.e("ACTION","down");
//獲取手指落下的坐標並保存
rawX = (int)(event.getRawX());
rawY = (int)(event.getRawY());
lastX = rawX;
lastY = rawY;
break;
case MotionEvent.ACTION_MOVE:
//Log.e("ACTION","move");
//手指拖動時,獲得當前位置
rawX = (int)event.getRawX();
rawY = (int)event.getRawY();
//手指移動的x軸和y軸偏移量分別為當前坐標-上次坐標
int offsetX = rawX - lastX;
int offsetY = rawY - lastY;
//通過View.layout來設置左上右下坐標位置
//獲得當前的left等坐標並加上相應偏移量
layout(getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY);
//移動過后,更新lastX與lastY
lastX = rawX;
lastY = rawY;
break;
}
return true;
}

//簡單的重寫onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(defaultWidth,defaultHeight);
}else if(widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(defaultWidth,heightSpecSize);
}else if(heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize, defaultHeight);
}else{
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
}
}
引用MyView的布局文件

<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.androidslide.MainActivity" >

<com.example.androidslide.MyView
android:background="#00ffff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<!-- 我們給View設置一個藍色的背景,方便比較View的區域和我們繪制的圓的區域 -->

</RelativeLayout>


邏輯並不復雜,大家結合注釋自己理解實驗一下


2.offsetLeftAndRight()offsetTopAndBottom()

這兩個也是View自帶的方法,根據方法名也很容易看出來操作對象是 左右,上下

因為左右移動是x軸移動上下移動是y軸移動

結合上一部分layout的代碼,我們只需稍作修改就可以

offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);

大家自己實踐一下


3.LayoutParams

因為View一定是在一個布局中,而且View的位置常常是由父布局來定的

LayoutParams保存了一個View的布局參數,所以我們可以通過改變LayoutParams來改變View的位置達到移動的效果

ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);

也可以通過具體的父布局,比如 LinearLayout.LayoutParams 或者 RelativeLayout.LayoutParams 來實現。


4. scrollTo 與 scrollBy(等寫完ViewGroup的自定義再講)

5.Scroller(和scrollTo 和 scrollBy一起講)

6.屬性動畫(單獨作為一篇文章總結動畫)

7.ViewDragHelper(要用到事件攔截機制,寫完事件分發和攔截文章再講)


對了,大家看完上述例子,千萬不要以為,移動小球坐標都一定要在onTouchEvent內寫。

你甚至可以在activity中,通過子線程不斷更新UI線程來不斷移動View實現跑馬燈的效果

多嘗試嘗試,有問題歡迎留言~


通過layout方法實現滑動的小球的源碼 點擊打開鏈接


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com