編輯:關於Android編程
在聯系人,好友等列表中,為了能夠快速的根據名稱查找到相應的聯系人或者好友,通常會建立一個可以根據字母快速定位名稱的View。如下圖中右邊字母表所示:
關於自定義View需要注意的事項:
讓View支持padding 讓View支持wrap_content 如果是View,只需重寫onDraw()方法;如果是ViewGroup,有必要重寫onMeasure()和onLayout()方法。在自定義字母索引View中,為了與普通的View有相同的屬性,當然需要支持padding;對於支持wrap_content,為了防止在這種自定義View情況下,使用wrap_content就相當於match_parent的情形,可以在onMeasure()中定義一個最小的默認的寬高度,以避免View顯示不正常;對於字母索引View需要重寫的方法,它是一個View,所以只需重寫onDraw()方法。
繪制字母時應該知道相關的信息:
字母的起始xy坐標 字母的大小繪制字母的要求:
居中顯示 可以改變字母的字體顏色 可以改變字母的字體大小下面為onDraw()方法中,繪制字母表的代碼:
int paddingLeft = getPaddingLeft();
//view實際寬度
int width = getWidth() - paddingLeft - getPaddingRight();
//繪制字符時的起始x坐標
float startX = paddingLeft + (float) width / 2;
int paddingTop = getPaddingTop();
//view實際高
int height = getHeight() - paddingTop - getPaddingBottom();
int length = mCharacters.length();
//每個字符所擁有的高度
float characterHeight = (float) height / length;
//繪制字符時的起始y坐標
float startY = paddingTop + characterHeight / 2;
//繪制的字符的界限
Rect bounds = new Rect();
mCharacterYValues = new int[length + 1];
for (int i = 0; i < length; i++) {
mPaint.getTextBounds(mCharacters, i, i + 1, bounds);
float x = startX - (float) (bounds.left + bounds.right) / 2;
float y = startY + i * characterHeight - (float) (bounds.top + bounds.bottom) / 2;
canvas.drawText(mCharacters, i, i + 1, x, y, mPaint);
//記錄每個字符起始的y坐標值
mCharacterYValues[i] = (int) (paddingTop + i * characterHeight);
}
//最後一個字符結束的y坐標值
mCharacterYValues[length] = (int) (paddingTop + length * characterHeight);
繪制字母的步驟:
1.求view可繪制的實際寬高度(去除padding)
2.求字母的寬高度
繪制的字符的界限 Rect bounds = new Rect();3.繪制字母
繪制字符時的起始x坐標startX = paddingLeft + (float) width / 2; 繪制字符時的起始y坐標startY = paddingTop + characterHeight / 2;(每個字符所擁有的高度characterHeight = (float) height/mCharacters.length()) 繪制字符
float x = startX - (float) (bounds.left + bounds.right) / 2;
float y = startY + i * characterHeight - (float) (bounds.top + bounds.bottom) / 2;
canvas.drawText(mCharacters, i, i + 1, x, y, mPaint);
效果圖:
1.定義觸摸事件監聽接口
/**
* 觸摸CharacterView事件的監聽接口
*/
public interface OnCharacterTouchListener {
/**
* 點擊字符回調的方法
*
* @param view CharacterView
* @param c 點擊的字符
*/
void onDown(View view, char c);
/**
* 在字符表上移動回調的方法
*
* @param view CharacterView
* @param c 當前手指觸摸的字符
*/
void onMove(View view, char c);
}
2.對View的觸摸事件的處理
/**
* 設置CharacterView的觸摸監聽器
*
* @param listener CharacterView觸摸監聽器
*/
public void setOnCharacterTouchListener(final OnCharacterTouchListener listener) {
if (listener == null) {
setOnTouchListener(null);
return;
}
setOnTouchListener(new View.OnTouchListener() {
char lastCharacter = ' ';
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int yDown = (int) motionEvent.getY();
if (yDown > mCharacterYValues[0] && yDown < mCharacterYValues[mCharacterYValues.length - 1]) {
char c = findCharacter(yDown);
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
listener.onDown(view, c);
break;
case MotionEvent.ACTION_MOVE:
if (lastCharacter == ' ' || lastCharacter != c) {
listener.onMove(view, c);
lastCharacter = c;
}
break;
case MotionEvent.ACTION_UP:
break;
}
}
return true;
}
});
}
/**
* 根據坐標值找到對應的字符
*
* @param yValue y坐標值
* @return 相應的字符
*/
private char findCharacter(int yValue) {
int low = 0, high = mCharacterYValues.length - 1;
int lastMid = 0;
while (low <= high) {
int mid = (low + high) / 2;
lastMid = mid;
if (mCharacterYValues[mid] == yValue) {
return mCharacters.charAt(mid);
} else if (mCharacterYValues[mid] < yValue) {
low = mid + 1;
} else {
high = mid - 1;
}
}
int closestPointIndex = lastMid;
//如果最後一次比較是大於目標值,則需要選擇前一個下標
if (mCharacterYValues[lastMid] > yValue) {
closestPointIndex -= 1;
}
return mCharacters.charAt(closestPointIndex);
}
主體思想:在繪制字符表的時候,記錄每一個字符的起始y坐標值,然後在觸摸事件中,把得到的y坐標值,與記錄的所有記錄的y坐標值進行比較,得到與觸摸事件中的y坐標值最相近的一個y坐標值,就可找到當前觸摸的字符。
例如: . A . B . C . D . E . . . Z .
在繪制的時候記錄從A前面的一個y坐標值到Z最後的一個y坐標值,在查找的時候采用二分查找算法,找到觸摸的y坐標值落在的區間,這個區間所代表的就是相應的字符。
//記錄每個字符起始的y坐標值
mCharacterYValues[i] = (int) (paddingTop + i * characterHeight);
//最後一個字符結束的y坐標值
mCharacterYValues[length] = (int) (paddingTop + length * characterHeight);
重寫onMeasure()方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//設置默認的寬度
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST) {
final int defaultWidth = 80;
setMeasuredDimension(defaultWidth, heightSpecSize);
}
}
不設置默認的高度的原因是:當View的layout_height=”wrap_content”時,可以讓它匹配父View的高度。
布局文件activity_main.xml:
<framelayout android:id="@+id/root_layout" android:layout_height="match_parent" android:layout_width="match_parent" android:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" tools:context="com.wj.study.MainActivity" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
</framelayout>
MainActivity代碼:
package com.wj.study;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.wj.study.view.CharacterView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CharacterView characterView = (CharacterView) findViewById(R.id.character_view);
characterView.insertFirst('#');
characterView.setBackgroundColor(Color.CYAN);
characterView.setColor(Color.BLUE);
characterView.setTextSize(getResources().getDimensionPixelSize(R.dimen.textSize_14sp));
characterView.setOnCharacterTouchListener(new CharacterView.OnCharacterTouchListener() {
@Override
public void onDown(View view, char c) {
Log.d("TAG", "onDown=" + c);
}
@Override
public void onMove(View view, char c) {
Log.d("TAG", "onMove=" + c);
Toast.makeText(view.getContext(), String.valueOf(c), Toast.LENGTH_SHORT).show();
}
});
}
}
CharacterView代碼:
package com.wj.study.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* Author:王江 on 2016/6/30 17:45
* Description: CharacterView主要是根據名稱首字母的做快速查找,通常應用於聯系人列表,好友列表等。
*/
public class CharacterView extends View {
private String mCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
private int[] mCharacterYValues = null;
public CharacterView(Context context) {
super(context);
}
public CharacterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@TargetApi(21)
public CharacterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public CharacterView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//設置默認的寬度
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST) {
final int defaultWidth = 80;
setMeasuredDimension(defaultWidth, heightSpecSize);
}
}
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(2.0f);
int paddingLeft = getPaddingLeft();
//view實際寬度
int width = getWidth() - paddingLeft - getPaddingRight();
//繪制字符時的起始x坐標
float startX = paddingLeft + (float) width / 2;
int paddingTop = getPaddingTop();
//view實際高
int height = getHeight() - paddingTop - getPaddingBottom();
int length = mCharacters.length();
//每個字符所擁有的高度
float characterHeight = (float) height / length;
//繪制字符時的起始y坐標
float startY = paddingTop + characterHeight / 2;
//繪制的字符的界限
Rect bounds = new Rect();
mCharacterYValues = new int[length + 1];
for (int i = 0; i < length; i++) {
mPaint.getTextBounds(mCharacters, i, i + 1, bounds);
float x = startX - (float) (bounds.left + bounds.right) / 2;
float y = startY + i * characterHeight - (float) (bounds.top + bounds.bottom) / 2;
canvas.drawText(mCharacters, i, i + 1, x, y, mPaint);
//記錄每個字符起始的y坐標值
mCharacterYValues[i] = (int) (paddingTop + i * characterHeight);
}
//最後一個字符結束的y坐標值
mCharacterYValues[length] = (int) (paddingTop + length * characterHeight);
}
/**
* 設置字符的字體大小
*
* @param textSize 字體大小
*/
public void setTextSize(float textSize) {
mPaint.setTextSize(textSize);
}
/**
* 設置畫筆的顏色
*
* @param color 畫筆顏色
*/
public void setColor(int color) {
mPaint.setColor(color);
}
/**
* 插入新一個字符,與原來的字符表組成一個新的字符表
*
* @param c 字符
*/
public void insertFirst(char c) {
StringBuilder sb = new StringBuilder();
sb.append(c);
sb.append(mCharacters);
mCharacters = null;
mCharacters = sb.toString();
}
/**
* 插入新一個字符,與原來的字符表組成一個新的字符表
*
* @param c 字符
*/
public void insertLast(char c) {
StringBuilder sb = new StringBuilder();
sb.append(mCharacters);
sb.append(c);
mCharacters = null;
mCharacters = sb.toString();
}
/**
* 自定義字符表
*
* @param characters 字符串
*/
public void setCharacter(String characters) {
if (characters == null) return;
mCharacters = null;
mCharacters = characters;
}
/**
* 設置CharacterView的觸摸監聽器
*
* @param listener CharacterView觸摸監聽器
*/
public void setOnCharacterTouchListener(final OnCharacterTouchListener listener) {
if (listener == null) {
setOnTouchListener(null);
return;
}
setOnTouchListener(new View.OnTouchListener() {
char lastCharacter = ' ';
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int yDown = (int) motionEvent.getY();
if (yDown > mCharacterYValues[0] && yDown < mCharacterYValues[mCharacterYValues.length - 1]) {
char c = findCharacter(yDown);
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
listener.onDown(view, c);
break;
case MotionEvent.ACTION_MOVE:
if (lastCharacter == ' ' || lastCharacter != c) {
listener.onMove(view, c);
lastCharacter = c;
}
break;
case MotionEvent.ACTION_UP:
break;
}
}
return true;
}
});
}
/**
* 根據坐標值找到對應的字符
*
* @param yValue y坐標值
* @return 相應的字符
*/
private char findCharacter(int yValue) {
int low = 0, high = mCharacterYValues.length - 1;
int lastMid = 0;
while (low <= high) {
int mid = (low + high) / 2;
lastMid = mid;
if (mCharacterYValues[mid] == yValue) {
return mCharacters.charAt(mid);
} else if (mCharacterYValues[mid] < yValue) {
low = mid + 1;
} else {
high = mid - 1;
}
}
int closestPointIndex = lastMid;
//如果最後一次比較是大於目標值,則需要選擇前一個下標
if (mCharacterYValues[lastMid] > yValue) {
closestPointIndex -= 1;
}
return mCharacters.charAt(closestPointIndex);
}
/**
* 觸摸CharacterView事件的監聽器
*/
public interface OnCharacterTouchListener {
/**
* 點擊字符回調的方法
*
* @param view CharacterView
* @param c 點擊的字符
*/
void onDown(View view, char c);
/**
* 在字符表上移動回調的方法
*
* @param view CharacterView
* @param c 當前手指觸摸的字符
*/
void onMove(View view, char c);
}
}
效果圖:
附加:對於該View如何與ListView(or RecyclerView)進行匹配,將在下一章中分析。
ContextMenu介紹: 如果一個View注冊了上下文菜單,那麼當長按該View時便會彈出一個浮動菜單,來供選擇下一步操作。 實現這個功能需要調用setOnCrea
本文主要實現在自定義的ListView布局中加入CheckBox控件,通過判斷用戶是否選中CheckBox來對ListView的選中項進行相應的操作。通過一個Demo來展
實際上字母索引表的效果,可以說在現在的眾多APP中使用的非常流行,比如支付寶,微信中的聯系人,還有購物,買票的APP中選擇全國城市,切換城市的時候,這時候的城市也就是按照
利用Android的ViewFlipper和AnimationUtils實現圖片帶有動畫的輪播切換,其中當點擊“上一張”圖片時,切換到上一張圖片;當點擊“下一張”圖片時,