自定義控件系列:
秒懂OnMeasure
秒懂OnLayout
讓自定義ViewGroup裏的子控件支持Margin
讓自定義ViewGroup支持Padding
自定義ViewGroup的一個綜合實踐 FlowLayout
onDraw
最簡單的自定義View:SwitchView
這次做一個SwitchView,需要使用下面的幾個知識點:
知識點:
自定義View不是每次都要重寫onMeasure和onLayout,爲了避免手寫onMeasure和onLayout,不要直接繼承ViewGroup,而是繼承LinearLayout、FrameLayout、RelativeLayout這種已經重寫過了onMeasure和onLayout的佈局
-
如何獲取控件的寬高
我們知道經常因爲控件還沒加載出來就去獲取寬高,那麼得到的寬高爲0,使用view.post()是獲取寬高最簡單的方式之一(爲什麼呢,看了源碼後,巴拉巴拉…),當然還有其他很多方法。
post(new Runnable() { @Override public void run() { mWidth = getMeasuredWidth(); mHeight = getMeasuredHeight(); } });
-
如何動態地添加一個子控件
我們經常需要通過代碼來添加子控件,而不是寫到xml裏,當然是addView了
addView(View child)
把子控件添加到末尾
void addView(View child, int index)
把子控件添加到指定位置
addView(View child, int width, int height)
把子控件添加到末尾,併爲子控件指定寬高
addView(View child, LayoutParams params)
把子控件添加到末尾,子控件的寬高、margin、gravity等等所有xml裏以“layout_xxx"開頭的屬性全部封裝在LayoutParams裏
addView(View child, int index, LayoutParams params)
把子控件添加到指定位置,子控件的寬高、margin、gravity等等所有xml裏以“layout_xxx"開頭的屬性全部封裝在LayoutParams裏
上面的方法都是間接或直接調用了這個方法可見LayoutParams也很重要
- 不同的ViewGroup都有自己專屬的LayoutParams,對應xml裏“layout_xxx"的所有屬性,我們如果想使用這些屬性,必須通過設置子控件的LayoutParams來實現
下圖是LinearLayout的Layout_開頭的屬性
下圖是RelativeLayut以Layout_開頭的屬性
舉個例子:在RelativeLayout裏動態添加子控件
rl = (RelativeLayout) findViewById(R.id.rl); TextView textView = new TextView(this); textView.setText("我是一個textview"); textView.setBackgroundColor(Color.RED); textView.setId(R.id.textview);//動態設置id,見https://www.cnblogs.com/codingblock/p/5090441.html RelativeLayout.LayoutParams lp1 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); lp1.leftMargin = 10; lp1.setMargins(100, 10, 10, 10); lp1.addRule(RelativeLayout.CENTER_IN_PARENT); textView.setLayoutParams(lp1); rl.addView(textView); ImageView imageView = new ImageView(this); imageView.setImageResource(R.mipmap.ic_launcher); RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); //★注意:這裏new的是RelativeLayout.LayoutParams,因爲你在爲RelativeLayout添加子控件 lp2.addRule(RelativeLayout.BELOW, R.id.textview); rl.addView(imageView, lp2);
- 不同的ViewGroup都有自己專屬的LayoutParams,對應xml裏“layout_xxx"的所有屬性,我們如果想使用這些屬性,必須通過設置子控件的LayoutParams來實現
-
重寫onTouchEvent,返回true,表示當前的view要處理事件(事件分發會詳細說這個方法)
-
如何讓view滑動起來
- 通過改變一個view的LayoutParams屬性值(這裏使用了margin),來實現改變view的位置
- 還可以通過scrollBy來實現,下篇博客舉這個例子
-
如何實現彈性滑動,即鬆手後view自己平滑地移動
- 屬性動畫逐漸地改變一個屬性,達到平滑移動效果
- scroller可以實現平滑移動
應用
效果
一個開關,滑塊隨着手指滑動,鬆手後,判斷滑動位置是不是過半,過半的話平滑移動到最右側,沒過半平滑移動到最左側
代碼
package com.view.custom.dosometest.view;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
import android.widget.LinearLayout;
/**
* 描述當前版本功能
*
* @Project: DoSomeTest
* @author: cjx
* @date: 2019-12-01 10:06 星期日
*/
public class SwitchView extends LinearLayout {
private ImageView mImageView;
private LayoutParams mLayoutParams;
private int mSliderHeight;
private int mSliderWidth;
private int mWidth;
private int mHeight;
public SwitchView(Context context) {
super(context);
init(context);
}
public SwitchView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SwitchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
setBackgroundColor(Color.GRAY);
post(new Runnable() {
@Override
public void run() {
addSlider();//因爲這裏需要用到控件的寬高,所以寫到post裏
}
});
}
private void addSlider() {
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
mSliderWidth = mWidth / 2;
mSliderHeight = mHeight;
// 注意導包LayoutParams是LinearLayout.LayoutParams
mLayoutParams = new LayoutParams(mSliderWidth, mSliderHeight);
mImageView = new ImageView(getContext());
mImageView.setBackgroundColor(Color.CYAN);
mImageView.setLayoutParams(mLayoutParams);
addView(mImageView);
}
float mLastX;
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
break;
case MotionEvent.ACTION_MOVE:
float deltaX = x - mLastX;
// 防止滑塊滑出邊界,矯正deltaX
if (mLayoutParams.leftMargin + mSliderWidth + deltaX > mWidth) {
deltaX = mWidth - mLayoutParams.leftMargin - mSliderWidth;
} else if (mLayoutParams.leftMargin + deltaX < 0) {
deltaX = -mLayoutParams.leftMargin;
}
// 動態改變這個控件的左邊距,實現控件的滑動
mLayoutParams.leftMargin += (int) deltaX;
//別忘了通過setLayoutParams,使你的改動生效
mImageView.setLayoutParams(mLayoutParams);
mLastX = x;
break;
case MotionEvent.ACTION_UP:
// 鬆手後,通過屬性動畫改變margin實現控件的平滑移動
if (mLayoutParams.leftMargin > mSliderWidth / 2) {
smoothChangeLeftMargin(mLayoutParams.leftMargin, mSliderWidth);
} else {
smoothChangeLeftMargin(mLayoutParams.leftMargin, 0);
}
break;
}
return true;
}
/**
* 使用屬性動畫平滑地過度leftMargin
* @param start
* @param end
*/
private void smoothChangeLeftMargin(int start, int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(start, end);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mLayoutParams.leftMargin = (int) animation.getAnimatedValue();
mImageView.setLayoutParams(mLayoutParams);
}
});
valueAnimator.setDuration(300);
valueAnimator.start();
}
}