轉眼都過去大半個月了,自己選擇了很多路,也想讓自己走出個名堂來。總是把很多事情都想得太美好。或者說一無所知的走下去必有收穫。最近在家裏閒着,工作也不好找,所以自己趁空餘時間學習了,在學習中看到了自定義視圖,覺得很不錯,分享給大家,我也是個小菜,希望能有更多的朋友一起來學習,這是我的QQ羣170548167。
先來描述一下功能:
1、點擊按鍵,可以播放音頻,所有這些都是有view類來管理的。
2、用戶可以使用多個手指點擊
先上圖:
這裏先看看第一部分,自定義View視圖:
public class PianoKeyborad extends View {
private Context mContext;
//最多支持5個手指彈奏
public static final int MAX_FINGERS = 5;
//設置5個黑色的按鍵
// public static final int BLACK_KEYS_COUNT = 5;
// 7個白色按鍵
public static final int WHITE_KEYS_COUNT = 7;
//黑色按鈕的寬比
public static final float BLACK_TO_WHITE_WIDTH_RATIO = 0.625f;
//黑色按鈕的高比
public static final float BLACK_TO_WHITE_HEIGHT_RATIO = 0.58f;
//設置最多支持5個手指彈奏
private Point[] mFingerPoints = new Point[MAX_FINGERS];
private int[] mFingerTones = new int[MAX_FINGERS];
//默認的顏色和按下的顏色(白色按下、黑色按下)
private Paint mWhiteKeyPaint, mWhiteKeyHitPaint,
mBlackKeyPaint, mBlackKeyHitPaint;
//設置音符的按鍵
private Paint mCKeyPaint, mCSharpKeyPaint, mDKeyPaint,
mDSharpKeyPaint, mEKeyPaint, mFKeyPaint,
mFSharpKeyPaint, mGKeyPaint, mGSharpKeyPaint,
mAKeyPaint, mASharpKeyPaint, mBKeyPaint;
private Rect mCKey = new Rect(), mCSharpKey = new Rect(),
mDKey = new Rect(), mDSharpKey = new Rect(),
mEKey = new Rect(), mFKey = new Rect(),
mFSharpKey = new Rect(), mGKey = new Rect(),
mGSharpKey = new Rect(), mAKey = new Rect(),
mASharpKey = new Rect(), mBKey = new Rect();
private MotionEvent.PointerCoords mPointerCoords;
public PianoKeyborad(Context context) {
super(context);
mContext = context;
}
public PianoKeyborad(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PianoKeyborad(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mPointerCoords = new MotionEvent.PointerCoords();
Arrays.fill(mFingerPoints, null);
Arrays.fill(mFingerTones, -1);
setupPaints();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
private void setupPaints() {
mWhiteKeyPaint = new Paint();
mWhiteKeyPaint.setStyle(Paint.Style.STROKE);
mWhiteKeyPaint.setColor(Color.BLACK);
mWhiteKeyPaint.setStrokeWidth(3);
mWhiteKeyPaint.setAntiAlias(true);
mCKeyPaint = mWhiteKeyPaint;
mDKeyPaint = mWhiteKeyPaint;
mEKeyPaint = mWhiteKeyPaint;
mFKeyPaint = mWhiteKeyPaint;
mGKeyPaint = mWhiteKeyPaint;
mAKeyPaint = mWhiteKeyPaint;
mBKeyPaint = mWhiteKeyPaint;
mWhiteKeyHitPaint = new Paint(mWhiteKeyPaint);
mWhiteKeyHitPaint.setColor(Color.LTGRAY);
mWhiteKeyHitPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mBlackKeyPaint = new Paint();
mBlackKeyPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mBlackKeyPaint.setColor(Color.BLACK);
mBlackKeyPaint.setAntiAlias(true);
mCSharpKeyPaint = mBlackKeyPaint;
mDSharpKeyPaint = mBlackKeyPaint;
mFSharpKeyPaint = mBlackKeyPaint;
mGSharpKeyPaint = mBlackKeyPaint;
mASharpKeyPaint = mBlackKeyPaint;
mBlackKeyHitPaint = new Paint(mBlackKeyPaint);
mBlackKeyHitPaint.setColor(Color.DKGRAY);
}
}
這段代碼顯示了PianoKeyBoard類所需的成員變量以及構造函數和onAttachedToWindow()、onDetachedFromWindow()回調。 接下來創建了不同的Paint對象,每個對象對應一個可能的按鍵狀態。另外注意的是,這裏爲每個按鍵創建了一個Rect成員對象,並用默認值初始化這些變量。
第二部分,onLayout()和onDraw()方法:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//獲取屏幕大小
int width = getWidth();
int height = getHeight();
//計算每個按鍵的大小
int whiteKeyWidth = width / WHITE_KEYS_COUNT;
int blackKeyWidth = (int) (whiteKeyWidth * BLACK_TO_WHITE_WIDTH_RATIO);
int blackKeyHeight = (int) (height * BLACK_TO_WHITE_HEIGHT_RATIO);
mCKey.set(0 * whiteKeyWidth, 0, 1 * whiteKeyWidth, height);
mDKey.set(1 * whiteKeyWidth, 0, 2 * whiteKeyWidth, height);
mEKey.set(2 * whiteKeyWidth, 0, 3 * whiteKeyWidth, height);
mFKey.set(3 * whiteKeyWidth, 0, 4 * whiteKeyWidth, height);
mGKey.set(4 * whiteKeyWidth, 0, 5 * whiteKeyWidth, height);
mAKey.set(5 * whiteKeyWidth, 0, 6 * whiteKeyWidth, height);
mBKey.set(6 * whiteKeyWidth, 0, 7 * whiteKeyWidth, height);
mCSharpKey.set(1 * whiteKeyWidth - (blackKeyWidth / 2), 0,
1 * whiteKeyWidth + (blackKeyWidth / 2), blackKeyHeight);
mDSharpKey.set(2 * whiteKeyWidth - (blackKeyWidth / 2), 0,
2 * whiteKeyWidth + (blackKeyWidth / 2), blackKeyHeight);
mFSharpKey.set(4 * whiteKeyWidth - (blackKeyWidth / 2), 0,
4 * whiteKeyWidth + (blackKeyWidth / 2), blackKeyHeight);
mGSharpKey.set(5 * whiteKeyWidth - (blackKeyWidth / 2), 0,
5 * whiteKeyWidth + (blackKeyWidth / 2), blackKeyHeight);
mASharpKey.set(6 * whiteKeyWidth - (blackKeyWidth / 2), 0,
6 * whiteKeyWidth + (blackKeyWidth / 2), blackKeyHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪製白色按鍵
canvas.drawRect(mCKey, mCKeyPaint);
canvas.drawRect(mDKey, mDKeyPaint);
canvas.drawRect(mEKey, mEKeyPaint);
canvas.drawRect(mFKey, mFKeyPaint);
canvas.drawRect(mGKey, mGKeyPaint);
canvas.drawRect(mAKey, mAKeyPaint);
canvas.drawRect(mBKey, mBKeyPaint);
//繪製黑色按鍵,這裏黑色按鍵會在白色按鍵上面
canvas.drawRect(mCSharpKey, mCSharpKeyPaint);
canvas.drawRect(mDSharpKey, mDSharpKeyPaint);
canvas.drawRect(mFSharpKey, mFSharpKeyPaint);
canvas.drawRect(mGSharpKey, mGSharpKeyPaint);
canvas.drawRect(mASharpKey, mASharpKeyPaint);
}
在onLayout()方法中,計算了每個按鍵的大小和位置。在onDraw()方法中,應避免執行任何耗時操作,而只關注實際的繪製,從而避免潛在的性能問題。
最後一部分,也就是添加點擊按鍵的效果了,onTouchEvent()方法:
@Override
public boolean onTouchEvent(MotionEvent event) {
//記錄用了多少個手指頭來點擊屏幕
int pointerCount = event.getPointerCount();
//有多個手指頭點擊屏幕,在此判斷。
//如果當前手指數量大於設置的最大數量
int cappedPointerCount = pointerCount > MAX_FINGERS ? MAX_FINGERS : pointerCount;
int actionIndex = event.getActionIndex();
int action = event.getActionMasked();
int id = event.getPointerId(actionIndex);
//檢查是否收到了手指的按下或者擡起的動作
if ((action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_POINTER_DOWN)
&& id < MAX_FINGERS) {
mFingerPoints[id] = new Point((int) event.getX(actionIndex),
(int) event.getY(actionIndex));
} else if ((action == MotionEvent.ACTION_POINTER_UP ||
action == MotionEvent.ACTION_UP)
&& id < MAX_FINGERS) {
mFingerPoints[id] = null;
invalidateKey(mFingerTones[id]);
mFingerTones[id] = -1;
}
for (int i = 0; i < cappedPointerCount; i++) {
int index = event.findPointerIndex(i);
if (mFingerPoints[i] != null && index != -1) {
mFingerPoints[i].set((int) event.getX(index),
(int) event.getY(index));
int tone = getToneForPoint(mFingerPoints[i]);
if (tone != mFingerTones[i] && tone != -1) {
invalidateKey(mFingerTones[i]);
mFingerTones[i] = tone;
invalidateKey(mFingerTones[i]);
if (!isKeyDown(i)) {
event.getPointerCoords(index, mPointerCoords);
Toast.makeText(mContext, "***" + mFingerTones[i], Toast.LENGTH_SHORT).show();
}
}
}
}
updatePaints();
return true;
}
這裏要說一下,對於每一個MotionEvent事件,都要檢查是否有新的手指觸摸了屏幕。如果有的話,需要新建一個Point對象,並把它存儲到數組中以便追蹤用戶的手指軌跡。如果有ACTION_UP事件發生,則需要移除相應的Point對象。
接下來需要遍歷所有通過MotionEvent追蹤的point,並檢查在上一次調用該方法後他們是否已被移除。