自定義view系列(3)--給自定義View新增點選事件
阿新 • • 發佈:2019-01-10
這幾天一直在看《android開發藝術探索》和《android群英傳》中關於自定義view的章節,結合著網上大神的一些心得分享,感覺自定義view這一塊受益匪淺,這裡做個心得筆記,主要記錄一下view與使用者互動的一些知識。
自定義view與使用者互動用的最多的就是單擊事件,其次的還有雙擊事件、長按事件、滑動事件等,所以就需要做好view的事件監聽。
如果我們繼承了View,也繪製好了控制元件,但是不重寫onTouchEvent()方法的話,設定點選事件一般也是沒用的,但也不是一定沒用,下面先介紹一種比較簡單的點選事件實現方式,也是從張鴻洋大神的一篇部落格中看到的。
設定view的點選事件實現方式一:
在View的構造方法中直接setOnClickListener,如下程式碼:
如上程式碼即可實現該自定義view的點選監聽,但是有很大的侷限性,首先這樣設定監聽很死板,不能供外部呼叫,其次經過測試,如果你這樣寫了程式碼,並且又重寫的該View的OnTouchEvent()方法,那麼不管你的onTouchEvent方法中是直接return super.onTouchEvent()還是做了任何其他邏輯,都不能觸發這個監聽事件,也就是說在這種情況下這種方式設定監聽根本沒用了,而這直接影響到了我們自定義View的擴充套件性,所以建議別這麼用,雖然簡單,但是麻煩事也多,不過這種方式也有一種適用情況,就是如果自定義View真的只需要自己內部處理邏輯,不需要外部的參與,那麼這樣設定也行,不過真的不推薦大家使用這種方式除非業務邏輯允許。public TouchEventTest(Context context) { this(context, null); } public TouchEventTest(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TouchEventTest(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Logger.e("view內部設定OnClickListener",""); } }); }
下面就介紹第二種給自定義view設定監聽的方式,也是個人認為比較正常且通用的一種方式。
即重寫onTouchEvent方法,然後判斷所點選的座標是否處於自定義view內部。如果處於自定義view內部,則回撥點選監聽介面,否則不予理會。
在說這種方式之前,先普及一下各種座標知識,畢竟一會要用到。
如上面的圖示:清楚的體現了各個座標的含義。
接著,我們需要重寫setOnClickListener()方法,程式碼如下:
@Override
public void setOnClickListener(OnClickListener l) {
mListener = l;
}
就是把OnClickListener賦值,當然我們也可以不使用Override註解,又或者我們可以自定義介面和方法,並提供一個設定自定義監聽的方法,程式碼如下:
public void setOnViewClick(onViewClick click) {
this.mViewClick = click;
}
public interface onViewClick {
/**
* @param scrollX 從按下到擡起,X軸方向移動的距離
* @param scrollY 從按下到擡起,Y軸方向移動的距離
*/
void onClick(float scrollX, float scrollY);
}
然後我們重寫onTouchEvent方法,如下是一個通用的設定點選事件模板方法,
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (x + getLeft() < getRight() && y + getTop() < getBottom()) {
mListener.onClick(this);
mViewClick.onClick(x - startX, y - startY);
}
break;
}
return true;
}
注意這裡一定要返回true或者根據自己的業務邏輯決定返回true還是false。根據view的事件分發流程,如果該自定義view不處理ACTION_DOWN事件,那麼以後的所有事件都不會給他處理,結合程式碼說一下:當用戶按下時,進入到MotionEvent.ACTION_DOWN這個case語句中,然後break掉,之後返回了true,重點就在這裡,如果這裡你返回了false,說明該view不處理事件,所以以後的各種狀態如ACTION_MOVE和ACTION_UP都不會再觸發,因為該事件已經交由該view的某個父類處理了,不會再傳遞給子view了。而如果返回了true,就說明該view接管了以後的所有操作,由該view處理相應的事件。
另外再說明一點:處理點選事件一般都是在ACTION_UP時進行,而處理滑動之類的一般都在ACTION_MOVE時進行!!!
下面我們在程式碼中驗證一下是否有效,程式碼如下(佈局檔案就不貼了):
public class TouchEventTestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_touch_event_test);
TouchEventTest testView = (TouchEventTest) findViewById(R.id.TouchEventTest);
testView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Logger.e("setOnClickListener,view.getClass():" + v.getClass() + "");
}
});
testView.setOnViewClick(new TouchEventTest.onViewClick() {
@Override
public void onClick(float scrollX, float scrollY) {
Logger.e("x軸移動了:" + scrollX + " px,y軸移動了:" + scrollY + " px", "");
}
});
}
}
我們看下打印出的Log資訊:
最後貼出完整程式碼,讓大家更好理解:
package com.lanma.customviewproject.views;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* 作者 qiang_xi on 2016/8/23 15:05.
*/
public class TouchEventTest extends View {
private OnClickListener mListener;
private onViewClick mViewClick;
private int startRawX;
private int startRawY;
public TouchEventTest(Context context) {
this(context, null);
}
public TouchEventTest(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TouchEventTest(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// this.setOnClickListener(new OnClickListener() {
// @Override
// public void onClick(View v) {
// Logger.e("view內部設定OnClickListener", "");
// }
// });
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startRawX = rawX;
startRawY = rawY;
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (x + getLeft() < getRight() && y + getTop() < getBottom()) {
mListener.onClick(this);
mViewClick.onClick(rawX - startRawX, rawY - startRawY);
}
break;
}
return true;
}
@Override
public void setOnClickListener(OnClickListener l) {
mListener = l;
}
public void setOnViewClick(onViewClick click) {
this.mViewClick = click;
}
public interface onViewClick {
/**
* @param scrollX 從按下到擡起,X軸方向移動的距離
* @param scrollY 從按下到擡起,Y軸方向移動的距離
*/
void onClick(float scrollX, float scrollY);
}
}