天天看點

android自定義view抽獎效果,Android 自定義View 之九宮格抽獎View

最近在項目中要實作一個九宮格抽獎view 。中間是抽獎按鈕,八個格子是獎品。效果圖如下:

android自定義view抽獎效果,Android 自定義View 之九宮格抽獎View

九宮格抽獎View

接下來我就分析一下實作這個View的步驟:

1.繪制出外框(此處難點是繪制閃光點的效果);

2.繪制九個格子,這個就是計算均分的邏輯,比較簡單。

3.實作抽獎動效,以及點選中間start按鈕有個縮放效果的實作。

我一一分析一下。

1.繪制外邊框:

見代碼:核心是使用  canvas.drawRoundRect(rectF, radiusBg, radiusBg, bgPaint);方法繪制圓角邊框矩形,繪制内外兩個邊框矩形,重疊在一起(此處起初想使用畫筆畫邊框,但實作起來隻能内邊框才有圓角,外邊框是直角)。

接下來就是繪制四個角上的小原點(原點是圖檔),這樣做是保證四個角的圖檔一緻,此處邏輯就是計算四個角上的位置稍微麻煩點;然後計算四條邊上的點。最後,使用postDelayed重複繪制,達到閃爍的效果。

public class LuckyDrawLayout extends RelativeLayout {

private static final StringTAG ="LuckyDrawLayout";

private Paint bgPaint;

private int mWidth, mHeight;

private int radiusBg;

private Rect FrectF =new RectF();

private Bitmap smallGreenBitmap;

private Bitmap smallRedBitmap;

private int ballWidth, ballHeight;

private int redBallWidth, redBallHeight;

private RectF ballRectf;

private int innerPadding =dip2px(15);

private boolean isChanged =true;

private int eachRow =13;

public LuckyDrawLayout(Context context) {

this(context, null);

}

public LuckyDrawLayout(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public LuckyDrawLayout(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

bgPaint =new Paint(Paint.ANTI_ALIAS_FLAG);

bgPaint.setStyle(Paint.Style.FILL);

bgPaint.setStrokeCap(Paint.Cap.ROUND);

bgPaint.setStrokeJoin(Paint.Join.ROUND);

bgPaint.setAntiAlias(true);

bgPaint.setDither(true);

bgPaint.setColor(0xFFFF356B);

smallGreenBitmap = BitmapFactory.decodeResource(getContext().getResources(), R.mipmap.ic_small_green);

smallRedBitmap = BitmapFactory.decodeResource(getContext().getResources(), R.mipmap.ic_small_red);

ballWidth =smallGreenBitmap.getWidth();

ballHeight =smallGreenBitmap.getHeight();

redBallWidth =smallRedBitmap.getWidth();

redBallHeight =smallRedBitmap.getHeight();

ballRectf =new RectF();

setWillNotDraw(false);

changeBall();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int mWidth = MeasureSpec.getSize(widthMeasureSpec);

int mHeight = MeasureSpec.getSize(heightMeasureSpec);

int size = Math.min(mWidth, mHeight);

setMeasuredDimension(size, size);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

mWidth = getWidth();

mHeight = getHeight();

radiusBg =mWidth /40;

rectF.set(0, 0, mWidth, mHeight);

bgPaint.setColor(0xFFFF356B);

canvas.drawRoundRect(rectF, radiusBg, radiusBg, bgPaint);

rectF.set(innerPadding, innerPadding, mWidth -innerPadding, mHeight -innerPadding);

bgPaint.setColor(0xFFCE0037);

canvas.drawRoundRect(rectF, radiusBg, radiusBg, bgPaint);

drawFourCorner(canvas);

int ballGapDp = (mWidth -innerPadding *2) /eachRow;

for (int i =0; i

if (getGreen(i)) {

//上

ballRectf.set(innerPadding *2 + i * ballGapDp -ballWidth /2, innerPadding /2 -ballHeight /2, innerPadding *2 +ballWidth /2 + i * ballGapDp, innerPadding /2 +ballHeight /2);

canvas.drawBitmap(smallGreenBitmap, null, ballRectf, null);

//下

ballRectf.set(innerPadding *2 + i * ballGapDp -ballWidth /2, mHeight - (innerPadding /2 +ballHeight /2), innerPadding *2 +ballWidth /2 + i * ballGapDp, mHeight - (innerPadding /2 -ballHeight /2));

canvas.drawBitmap(smallGreenBitmap, null, ballRectf, null);

//左

ballRectf.set(innerPadding /2 -ballWidth /2, innerPadding *2 + i * ballGapDp -ballHeight /2, innerPadding /2 +ballWidth /2, innerPadding *2 + i * ballGapDp +ballHeight /2);

canvas.drawBitmap(smallGreenBitmap, null, ballRectf, null);

//右

ballRectf.set(mWidth - (innerPadding /2 +ballWidth /2), innerPadding *2 + i * ballGapDp -ballHeight /2, mWidth - (innerPadding /2 -ballWidth /2), innerPadding *2 + i * ballGapDp +ballHeight /2);

canvas.drawBitmap(smallGreenBitmap, null, ballRectf, null);

}else {

ballRectf.set(innerPadding *2 + i * ballGapDp -redBallWidth /2, innerPadding /2 -redBallHeight /2, innerPadding *2 +redBallWidth /2 + i * ballGapDp, innerPadding /2 +redBallHeight /2);

canvas.drawBitmap(smallRedBitmap, null, ballRectf, null);

ballRectf.set(innerPadding *2 + i * ballGapDp -redBallWidth /2, mHeight - (innerPadding /2 +redBallHeight /2), innerPadding *2 +redBallWidth /2 + i * ballGapDp, mHeight - (innerPadding /2 -redBallHeight /2));

canvas.drawBitmap(smallRedBitmap, null, ballRectf, null);

ballRectf.set(innerPadding /2 -redBallWidth /2, innerPadding *2 + i * ballGapDp -redBallHeight /2, innerPadding /2 +redBallWidth /2, innerPadding *2 + i * ballGapDp +redBallHeight /2);

canvas.drawBitmap(smallRedBitmap, null, ballRectf, null);

ballRectf.set(mWidth - (innerPadding /2 +redBallWidth /2), innerPadding *2 + i * ballGapDp -redBallHeight /2, mWidth - (innerPadding /2 -redBallWidth /2), innerPadding *2 + i * ballGapDp +redBallHeight /2);

canvas.drawBitmap(smallRedBitmap, null, ballRectf, null);

}

}

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

super.onLayout(changed, l, t, r, b);

for (int i =0; i < getChildCount(); i++) {

View child = getChildAt(i);

if (childinstanceof LuckyDrawView) {

child.layout(innerPadding, innerPadding, getWidth() -innerPadding, getHeight() -innerPadding);

}

}

}

private void changeBall() {

postDelayed(new Runnable() {

@Override

public void run() {

isChanged = !isChanged;

Log.d(TAG, "run: changeBall()");

invalidate();

postDelayed(this, 300);

}

}, 300);

}

private boolean getGreen(int i) {

if (isChanged) {

return i %2 !=0;

}else {

return i %2 ==0;

}

}

private void drawFourCorner(Canvas canvas) {

if (isChanged) {

RectF leftTopRectf =new RectF(innerPadding /2 -ballWidth /2, innerPadding /2 -ballHeight /2, innerPadding /2 +ballWidth /2, innerPadding /2 +ballHeight /2);

canvas.drawBitmap(smallGreenBitmap, null, leftTopRectf, null);

RectF rightTopRectf =new RectF(mWidth - (innerPadding /2 +ballWidth /2), innerPadding /2 -ballHeight /2, mWidth - (innerPadding /2 -ballWidth /2), innerPadding /2 +ballHeight /2);

canvas.drawBitmap(smallGreenBitmap, null, rightTopRectf, null);

RectF leftBottomRectf =new RectF(innerPadding /2 -ballWidth /2, mHeight - (innerPadding /2 +ballHeight /2), innerPadding /2 +ballWidth /2, mHeight - (innerPadding /2 -ballHeight /2));

canvas.drawBitmap(smallGreenBitmap, null, leftBottomRectf, null);

RectF rightBottomRectf =new RectF(mWidth - (innerPadding /2 +ballWidth /2), mHeight - (innerPadding /2 +ballHeight /2), mWidth - (innerPadding /2 -ballWidth /2), mHeight - (innerPadding /2 -ballHeight /2));

canvas.drawBitmap(smallGreenBitmap, null, rightBottomRectf, null);

}else {

RectF leftTopRectf =new RectF(innerPadding /2 -redBallWidth /2, innerPadding /2 -redBallHeight /2, innerPadding /2 +redBallWidth /2, innerPadding /2 +redBallHeight /2);

canvas.drawBitmap(smallRedBitmap, null, leftTopRectf, null);

RectF rightTopRectf =new RectF(mWidth - (innerPadding /2 +redBallWidth /2), innerPadding /2 -redBallHeight /2, mWidth - (innerPadding /2 -redBallWidth /2), innerPadding /2 +redBallHeight /2);

canvas.drawBitmap(smallRedBitmap, null, rightTopRectf, null);

RectF leftBottomRectf =new RectF(innerPadding /2 -redBallWidth /2, mHeight - (innerPadding /2 +redBallHeight /2), innerPadding /2 +redBallWidth /2, mHeight - (innerPadding /2 -redBallHeight /2));

canvas.drawBitmap(smallRedBitmap, null, leftBottomRectf, null);

RectF rightBottomRectf =new RectF(mWidth - (innerPadding /2 +redBallWidth /2), mHeight - (innerPadding /2 +redBallHeight /2), mWidth - (innerPadding /2 -redBallWidth /2), mHeight - (innerPadding /2 -redBallHeight /2));

canvas.drawBitmap(smallRedBitmap, null, rightBottomRectf, null);

}

}

public static int dip2px(float dipValue) {

final float scale = Resources.getSystem().getDisplayMetrics().density;

return (int) (dipValue * scale +0.5f);

}

public static int px2sp(Context context, float pxValue) {

final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;

return (int) (pxValue / fontScale +0.5f);

}

public static int sp2px(Context context, float spValue) {

final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;

return (int) (spValue * fontScale +0.5f);

}

}

2.繪制九宮格:

先見代碼:主要就是計算每個格子的寬高,要均分,針對中間的格子做特殊處理,具體邏輯計算看下面drawNineCell 方法;然後繪制文本,居中顯示即可。接下來,實作轉動的邏輯,private int[]positions = {0, 1, 2, 5, 8, 7, 6, 3}; //順時針 這個記錄了轉動的順序,然後用變量currentPosition 記錄目前的位置下标,通過positions[currentPosition]取出對應的格子,然後繪制該格子的顯示樣式。開啟了一個線程計算currentPosition的值,為了實作一個快要中獎停頓的效果,線上程中轉動最後一圈的時候,使用SystemClock.sleep(100 * (currentPosition +1));讓子線程睡眠時間遞增,currentLoopCount記錄轉動的圈數,預設4圈;stopPosition停止的位置。最後實作點選中間按鈕縮放的效果,先計算中間按鈕的矩形位置mCenterButtonRectF,設定onTouchListener事件,計算點選的區域是否是在mCenterButtonRectF中,mCenterButtonRectF.contains(x, y)。如果在該區域中就執行縮放效果。具體看代碼。

public class LuckyDrawView extends View {

private static final StringTAG ="LuckyDrawView";

//0->1->2->3->5->6->7->8

//0-1-2-5-8-7-6-3

private int currentPosition =0;

private int stopPosition = -1;

private final static int LOOP_COUNT =4;

private int currentLoopCount =0;

private Paint bgPaint;

private int mWidth, mHeight;

private int radiusBg;

private Paint cellPaint;

private Paint cellTextPaint;

private int innerEachGap =dip2px(6);

private int innerWidth, innerHeight;

private int eachWidth, eachHeight;

private boolean onTouchCenter =false;

private RectFmCenterButtonRectF;

private String[]rewardTexts = {"$0.04", "$0.10", "$0.80", "$0.85", "", "$3.00", "$5.00", "$0.15", "$0.10"};

private int[]positions = {0, 1, 2, 5, 8, 7, 6, 3}; //順時針

String start ="Start";

float scale =1.0f;

private boolean isRuning =false;

public LuckyDrawView(Context context) {

this(context, null);

}

public LuckyDrawView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public LuckyDrawView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

bgPaint =new Paint(Paint.ANTI_ALIAS_FLAG);

bgPaint.setStyle(Paint.Style.FILL);

bgPaint.setStrokeCap(Paint.Cap.ROUND);

bgPaint.setStrokeJoin(Paint.Join.ROUND);

bgPaint.setAntiAlias(true);

bgPaint.setDither(true);

bgPaint.setColor(0xFFFF356B);

cellPaint =new Paint(Paint.ANTI_ALIAS_FLAG);

cellPaint.setStyle(Paint.Style.FILL);

cellPaint.setColor(Color.WHITE);

cellTextPaint =new Paint(Paint.ANTI_ALIAS_FLAG);

cellTextPaint.setTextSize(sp2px(context, 26));

cellTextPaint.setColor(Color.WHITE);

cellTextPaint.setTypeface(Typeface.DEFAULT_BOLD);

cellTextPaint.setAntiAlias(true);

setOnTouchListener(new OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

onTouchCenter =false;

int x = (int) event.getX();

int y = (int) event.getY();

if (mCenterButtonRectF.contains(x, y) && !isRuning) {

if (scale !=0.8f) {

scale =0.8f;

invalidate();

}

onTouchCenter =true;

}

break;

case MotionEvent.ACTION_UP:

if (onTouchCenter) {

startPressScaleAnim();

startLoop();

}

onTouchCenter =false;

break;

}

return true;

}

});

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

mWidth = getWidth();

mHeight = getHeight();

radiusBg =mWidth /40;

innerWidth =mWidth -innerEachGap *4;

innerHeight =mHeight -innerEachGap *4;

eachWidth =innerWidth /3;

eachHeight =innerHeight /3;

drawNineCell(canvas);

}

private void drawNineCell(Canvas canvas) {

int nums =9;

RectF rectF =new RectF();

for (int i =0; i < nums; i++) {

int startX =innerEachGap + (i %3) * (eachWidth +innerEachGap);

int startY =innerEachGap + (i /3) * (eachHeight +innerEachGap);

rectF.set(startX, startY, startX +eachWidth, startY +eachHeight);

if (i == nums /2) {

cellPaint.setColor(0xFFFFE535);

bgPaint.setColor(0xFFFF356B);

rectF.set(rectF.left + rectF.left * (1 -scale) *0.08f, rectF.top + rectF.top * (1 -scale) *0.08f, rectF.right - rectF.right * (1 -scale) *0.08f, rectF.bottom - rectF.bottom * (1 -scale) *0.08f);

canvas.drawRoundRect(rectF, radiusBg, radiusBg, cellPaint);

mCenterButtonRectF =new RectF(rectF);

rectF.set(rectF.left +dip2px(10), rectF.top +dip2px(10), rectF.right -dip2px(10), rectF.bottom -dip2px(10));

canvas.drawRoundRect(rectF, radiusBg, radiusBg, bgPaint);

cellTextPaint.setColor(Color.WHITE);

canvas.drawText(start, rectF.centerX() -cellTextPaint.measureText(start) /2, rectF.centerY() + getTextDiffY(cellTextPaint), cellTextPaint);

}else {

if (positions[currentPosition] == i) {

cellPaint.setColor(0xFFFBC01B);

cellTextPaint.setColor(Color.WHITE);

}else {

cellPaint.setColor(Color.WHITE);

cellTextPaint.setColor(0xFFFF5A00);

}

canvas.drawRoundRect(rectF, radiusBg, radiusBg, cellPaint);

canvas.drawText(rewardTexts[i], rectF.centerX() -cellTextPaint.measureText(rewardTexts[i]) /2, rectF.centerY() + getTextDiffY(cellTextPaint), cellTextPaint);

}

}

}

private float getTextDiffY(Paint paint) {

Paint.FontMetrics fontMetrics = paint.getFontMetrics();

return Math.abs(fontMetrics.descent - fontMetrics.ascent) /2 - fontMetrics.descent;

}

private void startLoop() {

currentLoopCount =0;

Random random =new Random();

stopPosition = random.nextInt(7);

currentPosition =0;

new Thread(action).start();

}

private Runnableaction =new Runnable() {

@Override

public void run() {

while (true) {

isRuning =true;

if (currentLoopCount >=LOOP_COUNT) {

isRuning =false;

postDelayed(new Runnable() {

@Override

public void run() {

Toast.makeText(getContext(), "恭喜你抽中了position=" +stopPosition +"(" +rewardTexts[positions[stopPosition]] +")", Toast.LENGTH_LONG).show();

}

}, 500);

break;

}

currentPosition++;

if (currentPosition >7) {

currentLoopCount++;

currentPosition =0;

}

post(new Runnable() {

@Override

public void run() {

invalidate();

}

});

if (currentLoopCount ==LOOP_COUNT -1) {

if (currentPosition %7 ==stopPosition) {

if (currentPosition ==stopPosition) {

currentLoopCount =LOOP_COUNT;

}

}

SystemClock.sleep(100 * (currentPosition +1));

}else {

SystemClock.sleep(100);

}

}

}

};

private void startPressScaleAnim() {

ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.8f, 1.0f);

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

scale = ((float) animation.getAnimatedValue());

invalidate();

}

});

valueAnimator.setDuration(300);

valueAnimator.start();

}

public static int dip2px(float dipValue) {

final float scale = Resources.getSystem().getDisplayMetrics().density;

return (int) (dipValue * scale +0.5f);

}

public static int sp2px(Context context, float spValue) {

final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;

return (int) (spValue * fontScale +0.5f);

}

}

不足之處:

1.繪制了兩層外框,中間的紅色區域繪制了兩次,導緻過渡繪制了。起初的想法是用bgPaint.setStyle(Paint.Style.Stroke);繪制邊框,然而繪制出的是内部是圓角,外角還是直角。

2.暫隻支援文本的顯示,未設定圖檔的顯示。