廢話不多說先看效果圖

立體方塊.gif
以前在網(wǎng)上看到前端有個(gè)3d立體相冊效果很好看,心想Android怎么能沒這個(gè)效果,這次就倉促花了兩天時(shí)間寫了這個(gè)控件。
這里貼代碼
public class Custom3DView extends ViewGroup {
private Camera mCamera = new Camera();//攝像機(jī)
private Matrix mMatrix = new Matrix();//矩陣
public Custom3DView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private int childViewMaxWidth = 0, childViewMaxHeight = 0;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int viewGroupWidth = 0, viewGroupHeight = 0;
measureChildren(widthMeasureSpec, heightMeasureSpec);//測量子view
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
childViewMaxWidth = childViewMaxWidth < childView.getMeasuredWidth() ? childView.getMeasuredWidth() : childViewMaxWidth;
childViewMaxHeight = childViewMaxHeight < childView.getMeasuredHeight() ? childView.getMeasuredHeight() : childViewMaxHeight;
}
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
switch (widthMode) {
case MeasureSpec.EXACTLY://match_parent 100dp 確切值 父決定子的確切大小,子被限定在給定的邊界里,忽略本身想要的大小
viewGroupWidth = widthSize > childViewMaxWidth ? widthSize : childViewMaxWidth;//選擇較大的值
break;
case MeasureSpec.AT_MOST://wrap_content 子最大可以達(dá)到的指定大小
viewGroupWidth = childViewMaxWidth;
break;
case MeasureSpec.UNSPECIFIED:// 父容器不對(duì)子View的大小做限制.
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY://match_parent 100dp 確切值 父決定子的確切大小,子被限定在給定的邊界里,忽略本身想要的大小
viewGroupHeight = heightSize > childViewMaxHeight ? heightSize : childViewMaxHeight;//選擇較大的值
break;
case MeasureSpec.AT_MOST://wrap_content 子最大可以達(dá)到的指定大小
viewGroupHeight = childViewMaxHeight;
break;
case MeasureSpec.UNSPECIFIED:// 父容器不對(duì)子View的大小做限制.
break;
}
setMeasuredDimension(viewGroupWidth, viewGroupHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left, right, top, bottom;
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
left = (getMeasuredWidth() - childView.getMeasuredWidth()) / 2;
right = (getMeasuredWidth() - childView.getMeasuredWidth()) / 2 + childView.getMeasuredWidth();
top = (getMeasuredHeight() - childView.getMeasuredHeight()) / 2;
bottom = (getMeasuredHeight() - childView.getMeasuredHeight()) / 2 + childView.getMeasuredHeight();
childView.layout(left, top, right, bottom);//所有childView居中疊加顯示
childView.setVisibility(GONE);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
drawChildView(canvas, 0);//0背面 1左面 2上面 3右面 4下面 5正面
drawChildView(canvas, 1);
drawChildView(canvas, 2);
drawChildView(canvas, 3);
drawChildView(canvas, 4);
drawChildView(canvas, 5);
}
private float rotateX, rotateY;
private void drawChildView(Canvas canvas, int page) {
View childView = getChildAt(page);
int childViewWidth = childView.getMeasuredWidth();
int childViewHeight = childView.getMeasuredHeight();
//坐標(biāo)軸中點(diǎn)在左上角
mCamera.save();//保存原先狀態(tài)
switch (page) {
case 0://背面
mCamera.rotateX(rotateX + 180);
mCamera.rotateY(-rotateY);//圖像圍繞Y軸旋轉(zhuǎn)
break;
case 1://左面
mCamera.rotateX(rotateX);
mCamera.rotateY(rotateY - 90);
break;
case 2://上面
mCamera.rotateX(rotateX + 90);
mCamera.rotateZ(rotateY);//圖像圍繞Z軸旋轉(zhuǎn)
break;
case 3://右面
mCamera.rotateX(rotateX);
mCamera.rotateY(rotateY + 90);
break;
case 4://下面
mCamera.rotateX(rotateX + 270);
mCamera.rotateZ(-rotateY);//圖像圍繞Z軸旋轉(zhuǎn)
break;
case 5://正面
mCamera.rotateX(rotateX);
mCamera.rotateY(rotateY);//圖像圍繞Y軸旋轉(zhuǎn)
break;
}
mMatrix.reset();
mCamera.setLocation(0, 0, -Integer.MAX_VALUE);//設(shè)置攝像機(jī)的位置。此處參數(shù)單位不是像素,而是 inch英寸/72px
mCamera.translate(0, 0, -childViewHeight / 2);//在所有三個(gè)軸上應(yīng)用平移變換。
mCamera.getMatrix(mMatrix);//將內(nèi)部的Matrix的值復(fù)制到matrix(注意必須在restore之前)
mCamera.restore();//恢復(fù)保存的狀態(tài)(如果有)
mMatrix.preTranslate(-getMeasuredWidth() / 2, -getMeasuredHeight() / 2);//在隊(duì)列頭部添加Translate
mMatrix.postTranslate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);//在隊(duì)列尾部添加Translate
//動(dòng)畫執(zhí)行順序,preTranslate讓childView中心移動(dòng)到坐標(biāo)軸中點(diǎn),rotateX,rotateY繞軸旋轉(zhuǎn),postTranslate讓childView回到原來位置,實(shí)現(xiàn)對(duì)稱旋轉(zhuǎn)
canvas.save();
canvas.concat(mMatrix);
switch (page) {//rotateX,rotateY浮動(dòng)90以內(nèi)畫出
case 0://背面
// 背面 y=180 x=0,y=0,x=180
if ((rotateY <= 270 && rotateY >= 90) && (rotateX <= 90 || rotateX >= 270))
drawChild(canvas, childView, getDrawingTime());
else if ((rotateY <= 90 || rotateY >= 270) && (rotateX >= 90 && rotateX <= 270))
drawChild(canvas, childView, getDrawingTime());
break;
case 1://左面
// 左面 y=90 x=0,y=270,x=180
if ((rotateY >= 0 && rotateY <= 180) && (rotateX <= 90 || rotateX >= 270))
drawChild(canvas, childView, getDrawingTime());
else if ((rotateY >= 180 && rotateY <= 360) && (rotateX >= 90 && rotateX <= 270))
drawChild(canvas, childView, getDrawingTime());
break;
case 2://上面
// 上面 x=270
if (rotateX >= 180 && rotateX <= 360)
drawChild(canvas, childView, getDrawingTime());
break;
case 3://右面
// 右面 y=270 x=0,y=90,x=180
if ((rotateY >= 180 && rotateY <= 360) && (rotateX <= 90 || rotateX >= 270))
drawChild(canvas, childView, getDrawingTime());
else if ((rotateY >= 0 && rotateY <= 180) && (rotateX >= 90 && rotateX <= 270))
drawChild(canvas, childView, getDrawingTime());
break;
case 4://下面
// 下面 x=90
if (rotateX >= 0 && rotateX <= 180)
drawChild(canvas, childView, getDrawingTime());
break;
case 5://正面
// 正面 y=0 x=0,y=180 x=180
if ((rotateY <= 90 || rotateY >= 270) && (rotateX <= 90 || rotateX >= 270))
drawChild(canvas, childView, getDrawingTime());
else if ((rotateY >= 90 && rotateY <= 270) && (rotateX >= 90 && rotateX <= 270))
drawChild(canvas, childView, getDrawingTime());
break;
}
canvas.restore();
}
float downX, downY, moveX, moveY;
//實(shí)現(xiàn)點(diǎn)擊子view手指不移動(dòng),子view點(diǎn)擊事件有效,其他情況子view點(diǎn)擊事件無效
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
float rawX = event.getRawX();//相對(duì)父容器
float rawY = event.getRawY();//相對(duì)父容器
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = rawX;
downY = rawY;
intercept = false;//按下不攔截
break;
case MotionEvent.ACTION_MOVE:
intercept = true;//滑動(dòng)自己處理
moveX = rawX;
moveY = rawY;
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(event);
}
private boolean intercept = false;//默認(rèn)不攔截
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (intercept) {//攔截
return intercept;
}
return super.onInterceptTouchEvent(event);
}
private float dy, lastDy;
private float dx, lastDx;
@Override
public boolean onTouchEvent(MotionEvent event) {
float rawX = event.getRawX();//相對(duì)父容器
float rawY = event.getRawY();//相對(duì)父容器
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = rawX;
downY = rawY;
break;
case MotionEvent.ACTION_MOVE:
moveX = rawX;
moveY = rawY;
dx = ((moveX - downX) + lastDx) % 360;
dy = (-(moveY - downY) + lastDy) % 360;
rotateX = dy < 0 ? dy % 360 + 360 : dy % 360;
rotateY = dx < 0 ? dx % 360 + 360 : dx % 360;
invalidate();
break;
case MotionEvent.ACTION_UP:
lastDx = dx;
lastDy = dy;
break;
}
return true;
}
public void stopAnimal() {
exitAnimal = true;
isAnimalRunning = false;
}
private boolean exitAnimal = true;
private boolean isAnimalRunning = false;
public void startAnimal() {
if (isAnimalRunning) {
return;
}
exitAnimal = false;
isAnimalRunning = true;
new Thread(new Runnable() {
@Override
public void run() {
while (!exitAnimal) {
try {
Thread.sleep(10);
lastDy += 1;
lastDx += 1;
dx = lastDx % 360;
dy = lastDy % 360;
rotateX = dy < 0 ? dy % 360 + 360 : dy % 360;
rotateY = dx < 0 ? dx % 360 + 360 : dx % 360;
postInvalidate();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
代碼還算比較簡單,第一次寫這個(gè)效果,歷時(shí)兩天,過程較艱苦,注釋寫的很清楚了,就不再贅述思路了, 有需要的可以直接復(fù)制粘貼。
下面這張圖的效果在項(xiàng)目的2.0控件中,和上面貼代碼的這個(gè)版本思路不太一樣,有興趣的可以去項(xiàng)目中看下代碼。

3d效果2.gif