完全自定義控件.(繼承View, ViewGroup)
* 1. 自定義開關
> - 1. 寫個類繼承View
> - 2. 拷貝包含包名的全路徑到xml中
> - 3. (MainActivity)界面中找到該控件, 設置初始信息
toggleView = (ToggleView) findViewById(R.id.toggleView);
// 設置開關更新監聽
toggleView.setOnSwitchStateUpdateListener(new OnSwitchStateUpdateListener(){
@Override
public void onStateUpdate(boolean state) {
Toast.makeText(getApplicationContext(), "state: " + state, 0).show();
}
});
}
> - 4. 根據需求繪製界面內容
* Android 的界面繪製流程
* 測量
擺放
繪製
* measure
->
layout
->
draw
*
|
|
|
* onMeasure -> onLayout -> onDraw 重寫這些方法, 實現自定義控件
*
* onResume()之後執行
*
* View
* onMeasure() (在這個方法裏指定自己的寬高) -> onDraw() (繪製自己的內容)
*
* ViewGroup
* onMeasure() (指定自己的寬高, 所有子View的寬高)-> onLayout() (擺放所有子View) -> onDraw() (繪製內容)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(switchBackgroupBitmap.getWidth(), switchBackgroupBitmap.getHeight());
}
// Canvas 畫布, 畫板. 在上邊繪製的內容都會顯示到界面上.
@Override
protected void onDraw(Canvas canvas) {
// 1. 繪製背景
canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint);
// 2. 繪製滑塊
if(isTouchMode){
// 根據當前用戶觸摸到的位置畫滑塊
// 讓滑塊向左移動自身一半大小的位置
float newLeft = currentX - slideButtonBitmap.getWidth() / 2.0f;
int maxLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
// 限定滑塊範圍
if(newLeft < 0){
newLeft = 0; // 左邊範圍
}else if (newLeft > maxLeft) {
newLeft = maxLeft; // 右邊範圍
}
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {
// 根據開關狀態boolean, 直接設置圖片位置
if(mSwitchState){// 開
int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {// 關
canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);
}
}
}
> - 5. 響應用戶的觸摸事件
// 重寫觸摸事件, 響應用戶的觸摸.
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isTouchMode = true;
System.out.println("event: ACTION_DOWN: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
System.out.println("event: ACTION_MOVE: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_UP:
isTouchMode = false;
System.out.println("event: ACTION_UP: " + event.getX());
currentX = event.getX();
float center = switchBackgroupBitmap.getWidth() / 2.0f;
// 根據當前按下的位置, 和控件中心的位置進行比較.
boolean state = currentX > center;
// 如果開關狀態變化了, 通知界面. 裏邊開關狀態更新了.
if(state != mSwitchState && onSwitchStateUpdateListener != null){
// 把最新的boolean, 狀態傳出去了
onSwitchStateUpdateListener.onStateUpdate(state);
}
mSwitchState = state;
break;
default:
break;
}
// 重繪界面
invalidate(); // 會引發onDraw()被調用, 裏邊的變量會重新生效.界面會更新
return true; // 消費了用戶的觸摸事件, 纔可以收到其他的事件.
}
> - 6. 創建一個狀態更新監聽
private OnSwitchStateUpdateListener onSwitchStateUpdateListener;
// 1. 聲明接口對象
public interface OnSwitchStateUpdateListener{// 狀態回調, 把當前狀態傳出去
void onStateUpdate(boolean state);
}
// 2. 添加設置接口對象的方法, 外部進行調用
public void setOnSwitchStateUpdateListener(
OnSwitchStateUpdateListener onSwitchStateUpdateListener) {
this.onSwitchStateUpdateListener = onSwitchStateUpdateListener;
}
// 3. 在合適的位置.執行接口的方法
onSwitchStateUpdateListener.onStateUpdate(state);
// 4. 界面/外部, 收到事件.
> - 7. 自定義屬性
1. 在attrs.xml聲明節點declare-styleable
<declare-styleable name="ToggleView">
<attr name="switch_background" format="reference" />
<attr name="slide_button" format="reference" />
<attr name="switch_state" format="boolean" />
</declare-styleable>
2. R會自動創建變量
attr 3個變量
styleable 一個int數組, 3個變量(保存位置)
3. 在xml配置聲明的屬性/ 注意添加命名空間
xmlns:app="http://schemas.android.com/apk/res/com.app.toggleview"
app:switch_background="@drawable/switch_background"
app:slide_button="@drawable/slide_button"
app:switch_state="false"
4. 在構造函數中獲取並使用
public ToggleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
// 獲取配置的自定義屬性
String namespace = "http://schemas.android.com/apk/res/com.app.toggleview";
int switchBackgroundResource = attrs.getAttributeResourceValue(namespace , "switch_background", -1);
int slideButtonResource = attrs.getAttributeResourceValue(namespace , "slide_button", -1);
mSwitchState = attrs.getAttributeBooleanValue(namespace, "switch_state", false);
setSwitchBackgroundResource(switchBackgroundResource);
setSlideButtonResource(slideButtonResource);
}
案例代碼展示:
/**
* 自定義開關
* Android 的界面繪製流程
* 測量 擺放
繪製
* measure ->
layout ->
draw
* | |
|
* onMeasure -> onLayout -> onDraw 重寫這些方法, 實現自定義控件
*
* onResume()之後執行
*
* View
* onMeasure() (在這個方法裏指定自己的寬高) -> onDraw() (繪製自己的內容)
*
* ViewGroup
* onMeasure() (指定自己的寬高, 所有子View的寬高)-> onLayout() (擺放所有子View) -> onDraw() (繪製內容)
*/
public class ToggleView extends View {
private Bitmap switchBackgroupBitmap; // 背景圖片
private Bitmap slideButtonBitmap; // 滑塊圖片
private Paint paint; // 畫筆
private boolean mSwitchState = false; // 開關狀態, 默認false
private float currentX;
/**
* 用於代碼創建控件
* @param context
*/
public ToggleView(Context context) {
super(context);
init();
}
/**
* 用於在xml裏使用, 可指定自定義屬性
* @param context
* @param attrs
*/
public ToggleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
// 獲取配置的自定義屬性
String namespace = "http://schemas.android.com/apk/res/com.itheima74.toggleview";
int switchBackgroundResource = attrs.getAttributeResourceValue(namespace , "switch_background", -1);
int slideButtonResource = attrs.getAttributeResourceValue(namespace , "slide_button", -1);
mSwitchState = attrs.getAttributeBooleanValue(namespace, "switch_state", false);
setSwitchBackgroundResource(switchBackgroundResource);
setSlideButtonResource(slideButtonResource);
}
/**
* 用於在xml裏使用, 可指定自定義屬性, 如果指定了樣式, 則走此構造函數
* @param context
* @param attrs
* @param defStyle
*/
public ToggleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
paint = new Paint();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(switchBackgroupBitmap.getWidth(), switchBackgroupBitmap.getHeight());
}
// Canvas 畫布, 畫板. 在上邊繪製的內容都會顯示到界面上.
@Override
protected void onDraw(Canvas canvas) {
// 1. 繪製背景
canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint);
// 2. 繪製滑塊
if(isTouchMode){
// 根據當前用戶觸摸到的位置畫滑塊
// 讓滑塊向左移動自身一半大小的位置
float newLeft = currentX - slideButtonBitmap.getWidth() / 2.0f;
int maxLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
// 限定滑塊範圍
if(newLeft < 0){
newLeft = 0; // 左邊範圍
}else if (newLeft > maxLeft) {
newLeft = maxLeft; // 右邊範圍
}
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {
// 根據開關狀態boolean, 直接設置圖片位置
if(mSwitchState){// 開
int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {// 關
canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);
}
}
}
boolean isTouchMode = false;
private OnSwitchStateUpdateListener onSwitchStateUpdateListener;
// 重寫觸摸事件, 響應用戶的觸摸.
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isTouchMode = true;
System.out.println("event: ACTION_DOWN: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
System.out.println("event: ACTION_MOVE: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_UP:
isTouchMode = false;
System.out.println("event: ACTION_UP: " + event.getX());
currentX = event.getX();
float center = switchBackgroupBitmap.getWidth() / 2.0f;
// 根據當前按下的位置, 和控件中心的位置進行比較.
boolean state = currentX > center;
// 如果開關狀態變化了, 通知界面. 裏邊開關狀態更新了.
if(state != mSwitchState && onSwitchStateUpdateListener != null){
// 把最新的boolean, 狀態傳出去了
onSwitchStateUpdateListener.onStateUpdate(state);
}
mSwitchState = state;
break;
default:
break;
}
// 重繪界面
invalidate(); // 會引發onDraw()被調用, 裏邊的變量會重新生效.界面會更新
return true; // 消費了用戶的觸摸事件, 纔可以收到其他的事件.
}
/**
* 設置背景圖
* @param switchBackground
*/
public void setSwitchBackgroundResource(int switchBackground) {
switchBackgroupBitmap = BitmapFactory.decodeResource(getResources(), switchBackground);
}
/**
* 設置滑塊圖片資源
* @param slideButton
*/
public void setSlideButtonResource(int slideButton) {
slideButtonBitmap = BitmapFactory.decodeResource(getResources(), slideButton);
}
/**
* 設置開關狀態
* @param b
*/
public void setSwitchState(boolean mSwitchState) {
this.mSwitchState = mSwitchState;
}
public interface OnSwitchStateUpdateListener{
// 狀態回調, 把當前狀態傳出去
void onStateUpdate(boolean state);
}
public void setOnSwitchStateUpdateListener(
OnSwitchStateUpdateListener onSwitchStateUpdateListener) {
this.onSwitchStateUpdateListener = onSwitchStateUpdateListener;
}
}
MainActivity.java
public class MainActivity extends Activity {
private ToggleView toggleView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toggleView = (ToggleView) findViewById(R.id.toggleView);
// toggleView.setSwitchBackgroundResource(R.drawable.switch_background);
// toggleView.setSlideButtonResource(R.drawable.slide_button);
// toggleView.setSwitchState(true);
//
// 設置開關更新監聽
toggleView.setOnSwitchStateUpdateListener(new OnSwitchStateUpdateListener(){
@Override
public void onStateUpdate(boolean state) {
Toast.makeText(getApplicationContext(), "state: " + state, 0).show();
}
});
}
// @Override
// protected void onResume() {
// super.onResume();
// }
//
}