今日は以前実装した連絡先のアルファベットナビゲーションコントロールを共有します。以下に、連絡先に似たアルファベットナビゲーションビューをカスタマイズする方法を示します。カスタマイズする必要がある要素には、アルファベットインジケーターの描画、テキストの描画、タッチリスナー、座標計算などがあります。カスタマイズが完了した後に達成できる機能は以下の通りです:
- リストデータとアルファベット間の相互連動を実現する。
- レイアウトファイルの属性設定をサポートする。
- レイアウトファイル内で、アルファベットの色、フォントサイズ、インジケーターの色などの関連属性を設定できる。
主な内容は以下の通りです:
- カスタム属性
- 測定
- 座標計算
- 描画
- 表示効果
カスタム属性#
value の下に attr.xml を作成し、カスタマイズする必要がある属性を設定します。具体的には以下の通りです:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LetterView">
<!--アルファベットの色-->
<attr name="letterTextColor" format="color" />
<!--アルファベットのフォントサイズ-->
<attr name="letterTextSize" format="dimension" />
<!--全体の背景-->
<attr name="letterTextBackgroundColor" format="color" />
<!--インジケーターを有効にするかどうか-->
<attr name="letterEnableIndicator" format="boolean" />
<!--インジケーターの色-->
<attr name="letterIndicatorColor" format="color" />
</declare-styleable>
</resources>
次に、対応するコンストラクタ内でこれらの属性を取得し、関連する属性を設定します。具体的には以下の通りです:
public LetterView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//属性を取得
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LetterView);
int letterTextColor = array.getColor(R.styleable.LetterView_letterTextColor, Color.RED);
int letterTextBackgroundColor = array.getColor(R.styleable.LetterView_letterTextBackgroundColor, Color.WHITE);
int letterIndicatorColor = array.getColor(R.styleable.LetterView_letterIndicatorColor, Color.parseColor("#333333"));
float letterTextSize = array.getDimension(R.styleable.LetterView_letterTextSize, 12);
enableIndicator = array.getBoolean(R.styleable.LetterView_letterEnableIndicator, true);
//デフォルト設定
mContext = context;
mLetterPaint = new Paint();
mLetterPaint.setTextSize(letterTextSize);
mLetterPaint.setColor(letterTextColor);
mLetterPaint.setAntiAlias(true);
mLetterIndicatorPaint = new Paint();
mLetterIndicatorPaint.setStyle(Paint.Style.FILL);
mLetterIndicatorPaint.setColor(letterIndicatorColor);
mLetterIndicatorPaint.setAntiAlias(true);
setBackgroundColor(letterTextBackgroundColor);
array.recycle();
}
測定#
カスタムサイズと座標を正確に制御するためには、現在のカスタムビューの幅と高さを測定する必要があります。その後、測定されたサイズを使用して関連する座標を計算できます。具体的な測定プロセスは、View を継承して onMeasure () メソッドをオーバーライドして測定を完了します。重要なコードは以下の通りです:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//幅と高さのサイズを取得
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//wrap_contentのデフォルト幅と高さ
@SuppressLint("DrawAllocation") Rect mRect = new Rect();
mLetterPaint.getTextBounds("A", 0, 1, mRect);
mWidth = mRect.width() + dpToPx(mContext, 12);
int mHeight = (mRect.height() + dpToPx(mContext, 5)) * letters.length;
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT &&
getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, mHeight);
} else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, heightSize);
} else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(widthSize, mHeight);
}
mWidth = getMeasuredWidth();
int averageItemHeight = getMeasuredHeight() / 28;
int mOffset = averageItemHeight / 30; //画面調整
mItemHeight = averageItemHeight + mOffset;
}
座標計算#
カスタムビューは実際には、ビュー上で適切な位置を見つけ、カスタム要素を順序よく描画することです。描画プロセスで最も難しいのは、具体的な要件に基づいて適切な左側を計算することです。描画は API の呼び出しであり、座標位置が計算されれば、カスタムビューの描画部分には問題がないはずです。以下の図は、アルファベットインジケーターの描画の中心位置座標の計算とテキスト描画の起点位置計算を示しています。描画プロセスでは、テキストがインジケーターの中心位置にあることを保証する必要があります。参考は以下の通りです:
描画#
カスタムビューの描画操作はすべて onDraw () メソッド内で行われます。ここでは主に円の描画とテキストの描画を使用します。具体的には drawCircle () と drawText () メソッドの使用です。テキストが隠れないように、まずアルファベットインジケーターを描画し、その後にアルファベットを描画します。コードは以下の通りです:
@Override
protected void onDraw(Canvas canvas) {
//アルファベットの幅と高さを取得
@SuppressLint("DrawAllocation") Rect rect = new Rect();
mLetterPaint.getTextBounds("A", 0, 1, rect);
int letterWidth = rect.width();
int letterHeight = rect.height();
//インジケーターを描画
if (enableIndicator){
for (int i = 1; i < letters.length + 1; i++) {
if (mTouchIndex == i) {
canvas.drawCircle(0.5f * mWidth, i * mItemHeight - 0.5f * mItemHeight, 0.5f * mItemHeight, mLetterIndicatorPaint);
}
}
}
//アルファベットを描画
for (int i = 1; i < letters.length + 1; i++) {
canvas.drawText(letters[i - 1], (mWidth - letterWidth) / 2, mItemHeight * i - 0.5f * mItemHeight + letterHeight / 2, mLetterPaint);
}
}
これで、ビューの基本的な描画が終了しました。現在、カスタムビューのインターフェースが表示されるようになりましたが、まだ関連するイベント操作は追加されていません。次に、ビューのタッチイベント内で関連するロジックを実装します。
タッチイベント処理#
指が現在どのアルファベットに対応しているかを判断するためには、現在のタッチ座標を取得してアルファベットインデックスを計算する必要があります。onTouchEvent () メソッドを再定義し、MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE をリッスンしてインデックス位置を計算し、MotionEvent.ACTION_UP をリッスンして結果をコールバックします。具体的には以下の通りです:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
isTouch = true;
int y = (int) event.getY();
Log.i("onTouchEvent","--y->" + y + "-y-dp-->" + DensityUtil.px2dp(getContext(), y));
int index = y / mItemHeight;
if (index != mTouchIndex && index < 28 && index > 0) {
mTouchIndex = index;
Log.i("onTouchEvent","--mTouchIndex->" + mTouchIndex + "--position->" + mTouchIndex);
}
if (mOnLetterChangeListener != null && mTouchIndex > 0) {
mOnLetterChangeListener.onLetterListener(letters[mTouchIndex - 1]);
}
invalidate();
break;
case MotionEvent.ACTION_UP:
isTouch = false;
if (mOnLetterChangeListener != null && mTouchIndex > 0) {
mOnLetterChangeListener.onLetterDismissListener();
}
break;
}
return true;
}
これで、ビューのカスタマイズの重要な部分が基本的に完成しました。
データ組み立て#
アルファベットナビゲーションの基本的な考え方は、特定のフィールドをアルファベットに変換し、そのフィールドに基づいてデータをソートすることです。最終的に、特定のデータフィールドの最初のアルファベットを使用して、同じ最初のアルファベットのデータを一致させることができます。ここでは、漢字をピンインに変換するために pinyin4j-2.5.0.jar を使用し、データ項目を最初のアルファベットに基づいてソートしてデータを表示します。漢字をピンインに変換する方法は以下の通りです:
//漢字をピンインに変換
public static String getChineseToPinyin(String chinese) {
StringBuilder builder = new StringBuilder();
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
format.setCaseType(HanyuPinyinCaseType.UPPERCASE);
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
char[] charArray = chinese.toCharArray();
for (char aCharArray : charArray) {
if (Character.isSpaceChar(aCharArray)) {
continue;
}
try {
String[] pinyinArr = PinyinHelper.toHanyuPinyinStringArray(aCharArray, format);
if (pinyinArr != null) {
builder.append(pinyinArr[0]);
} else {
builder.append(aCharArray);
}
} catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {
badHanyuPinyinOutputFormatCombination.printStackTrace();
builder.append(aCharArray);
}
}
return builder.toString();
}
データのソートには Comparator インターフェースを使用すればよく、ここでは詳細には触れません。具体的なソースコードのリンクを文末で確認してください。
表示効果#
表示効果は以下の通りです: