特殊な効果や機能が必要な場合がありますが、システム コントロールではニーズを満たせないため、自分でコントロールを定義する必要があります。
カスタムビューのプロセス
ビューの継承
View をカスタマイズするには、まず View またはそのサブクラスを継承する必要があります。達成する必要がある効果がより複雑な場合は、通常、View を継承する必要があります。システム コントロールといくつかの特殊効果が必要な場合もあります。その場合は、View サブクラスを継承できます。 (TextViewなど)
自分でレイアウトをデザインしたり、他のコントロールを組み合わせたりする場合は、ViewGroup または LinearLayout、FrameLayout などのシステムに付属するレイアウトを継承する必要があります。
オーバーライドコンストラクター
カスタム ビューは少なくとも 2 つの構築メソッドをオーバーライドする必要があります
- Java コードでコントロールを直接作成するために使用されるコンストラクター
public CustomView(Context context) {
this(context, null);
}
- Viewで使用するコンストラクターをxmlファイルで定義します。
public CustomView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
}
カスタム XML の属性
まず、新しい res/values/custom_view_attrs.xml を作成し、次のように宣言する必要があります。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomView">
<attr name="custom_width" format="dimension" />
</declare-styleable>
</resources>
次に、それを XML レイアウト ファイルで宣言できます。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cn.lkllkllkl.customview.MainActivity">
<cn.lkllkllkl.customview.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:custom_width="100dp"/>
</LinearLayout>
次に、このプロパティの値を取り出して、カスタム ビューのコンストラクターで使用できます。
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
mPaint = new Paint();
float customWidth = 0;
TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
customWidth = typeArray.getDimension(R.styleable.CustomView_custom_width, 100);
// 需要记得回收
typeArray.recycle();
}
onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
スペックの測定
ここで onMeasure の 2 つのパラメーターを理解するには、まず MeasureSpec クラスを理解する必要があります。
MeasureSpec は、SpecMode (モード) と SpecSize (特定のサイズ) を int (つまり、widthMeasureSpec、heightMeasureSpec) にパックします。最初の 2 桁は SpecMode を表し、最後の 30 桁はサイズを表します。
SpecModeには3種類あります
-
未指定:親コンテナにはビューのサイズに制限がありません。通常、使用するカスタム ビューは少なくなります。
-
EXACTLY: xml ファイルの dp で特定のサイズを設定するか、match_parent に設定されている場合は SpecMode を EXACTLY に設定します。このモードは、SpecSize がカスタム ビューのサイズとして直接使用されることを意味します
-
AT_MOST: XML ファイルのwrap_contentに対応し、設定サイズがSpecSizeを超えることができないことを示します。
通常、wrap_content をサポートするビューが必要な場合にのみ onMeasure を書き換える必要があります。書き換えない場合、wrap_content の効果は match_parent と同じです。通常、次のコードで十分です
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// dp转换成px, 事先定义的默认宽高单位为dp
int defaultWidth = dp2px(getContext(), DEFAULT_WIDTH);
int defaultHeight = dp2px(getContext(), DEFAULT_HEIGHT);
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
widthSize = Math.min(defaultWidth, widthSize);
heightSize = Math.min(defaultHeight, heightSize);
} else if (widthMode == MeasureSpec.AT_MOST) {
widthSize = Math.min(defaultWidth, widthSize);
} else if (heightMode == MeasureSpec.AT_MOST){
heightSize = Math.min(defaultHeight, heightSize);
}
setMeasuredDimension(widthSize, heightSize);
}
一般に、さまざまな画面に適応するには、dp を単位として使用する必要があり、setMeasuredDimension は px を単位として使用するため、上記のコードは設定したデフォルトの幅と高さを px に変換します。変換方法は次のとおりです。
public static int dp2px(Context context, float dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dp, context.getResources().getDisplayMetrics());
}
onLayout メソッド
一般に、カスタム ViewGroup は、サブビューを放出するためにこのメソッドを書き直す必要があります。
onDraw メソッド
onDraw メソッドを使用すると、ビューを画面に描画できます。ビューでパディング属性をサポートしたい場合は、onDraw で行う必要があります。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/* mPaint为成员变量,画笔Paint的对象,
* 在构造函数中进行初始化,可以设置一些在canvas上绘制的通用属性
*/
mPaint.setColor(Color.RED);
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
//在View所占区域绘制一条对角线
canvas.drawLine(paddingLeft, paddingTop,
getWidth() - paddingRight, getHeight() - paddingBottom, mPaint);
}
予防
-
onMeasure、onLayout、onDraw の 3 つのメソッド、特に onDraw メソッドがこれを呼び出す可能性があるため、頻繁なメモリ割り当てによるメモリ ジッターを避けるため、またはフレームを引き起こす時間のかかる操作を実行するために、これらのメソッドでオブジェクトを作成しないことが最善です。スキップ
-
View には post series メソッドがあるため、View でハンドラーを使用する必要はありません
-
View に時間内に停止する必要がある作成スレッドまたはアニメーションがある場合は、View#onDetachedFromWindow が適しています。
-
ビューにスライディングのネストがある場合、スライディングの競合を適切に処理する必要があります。
参照
-
「Android開発アート探訪」