Android 自定义3D效果View
程序员文章站
2022-05-30 20:09:53
...
由于工作需要,今天就使用Camera与Matrix实现3D效果
案例图:
1.自定义View
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
public class ThreeDLayout extends ViewGroup {
private Camera mCamera;
private Matrix mMatrix;
//this viewgroup's center
private int mCenterX;
private int mCenterY;
//rotateDegree
private float mCanvasRotateY;
private float mCanvasRotateX;
private float mCanvasMaxRotateDegree = 50;
//the touch mode
public static int MODE_X = 0;
public static int MODE_Y = 1;
public static int MODE_BOTH_X_Y = 2;
private int mMode = MODE_BOTH_X_Y;
private float mDensity;
private float[] mValues = new float[9];
//the flag of touch
private boolean isCanTouch = false;
//the degree of animation
private float mDegreeY = 0;
private float mDegreeX = 0;
//the flag of animate
private boolean isPlaying = false;
//the degree of longer animate
private int mLoopAnimateY = 0;
public ThreeDLayout(Context context) {
this(context, null);
}
public ThreeDLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ThreeDLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//set a default background to make sure onDraw() dispatch
if (getBackground() == null) {
setBackgroundColor(Color.parseColor("#ffffff"));
}
DisplayMetrics dm = new DisplayMetrics();
dm = getResources().getDisplayMetrics();
mDensity = dm.density;
mCamera = new Camera();
mMatrix = new Matrix();
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (getChildCount() != 1) {
throw new IllegalStateException("ThreeDLayout can only have one child");
}
View child = getChildAt(0);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
//only one child view,so give the same size
setMeasuredDimension(child.getMeasuredWidth(), child.getMeasuredHeight());
}
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
View child = getChildAt(0);
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2;
mCenterY = h / 2;
}
@Override protected void onDraw(Canvas canvas) {
mMatrix.reset();
mCamera.save();
if (mMode == MODE_Y || mMode == MODE_BOTH_X_Y) {
mCamera.rotateX(mCanvasRotateX);
}
if (mMode == MODE_X || mMode == MODE_BOTH_X_Y) {
mCamera.rotateY(mCanvasRotateY);
}
mCamera.rotateY(mDegreeY);
mCamera.rotateX(mDegreeX);
if (isPlaying) {
mCamera.rotateY(mLoopAnimateY++);
Log.e("wing", mLoopAnimateY + "");
if (mLoopAnimateY == 360) {
mLoopAnimateY = 0;
}
invalidate();
}
mCamera.getMatrix(mMatrix);
// fix the Camera bug,
mMatrix.getValues(mValues);
mValues[6] = mValues[6] / mDensity;
mValues[7] = mValues[7] / mDensity;
mMatrix.setValues(mValues);
mCamera.restore();
mMatrix.preTranslate(-mCenterX, -mCenterY);
mMatrix.postTranslate(mCenterX, mCenterY);
canvas.concat(mMatrix);
super.onDraw(canvas);
}
@Override public boolean onInterceptTouchEvent(MotionEvent ev) {
if (isCanTouch) {
return true;
} else {
return super.onInterceptTouchEvent(ev);
}
}
@Override public boolean onTouchEvent(MotionEvent event) {
if (isCanTouch) {
float x = event.getX();
float y = event.getY();
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE: {
rotateCanvasWhenMove(x, y);
invalidate();
return true;
}
case MotionEvent.ACTION_UP: {
mDegreeY = 0;
rotateCanvasWhenMove(mCenterX, mCenterY);
invalidate();
return true;
}
}
return true;
} else {
return super.onTouchEvent(event);
}
}
/**
* get the value to rotate
*/
private void rotateCanvasWhenMove(float x, float y) {
float dx = x - mCenterX;
float dy = y - mCenterY;
float percentX = dx / mCenterX;
float percentY = dy / mCenterY;
if (percentX > 1f) {
percentX = 1f;
} else if (percentX < -1f) {
percentX = -1f;
}
if (percentY > 1f) {
percentY = 1f;
} else if (percentY < -1f) {
percentY = -1f;
}
mCanvasRotateY = mCanvasMaxRotateDegree * percentX;
mCanvasRotateX = -(mCanvasMaxRotateDegree * percentY);
}
public void setTouchable(boolean canTouch) {
isCanTouch = canTouch;
}
public void setTouchMode(int mode) {
mMode = mode;
isCanTouch = true;
}
/**
* set the max rotate degree
*/
public void setMaxRotateDegree(int degree) {
mCanvasMaxRotateDegree = degree;
}
/**
* start horizontal turn animate
*/
public void startHorizontalAnimate(long duration) {
final ValueAnimator animator = ValueAnimator.ofFloat(-180f, 0f);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
mDegreeY = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override public void onAnimationStart(Animator animation) {
}
@Override public void onAnimationEnd(Animator animation) {
mDegreeY = 0;
animator.removeAllUpdateListeners();
}
@Override public void onAnimationCancel(Animator animation) {
}
@Override public void onAnimationRepeat(Animator animation) {
}
});
animator.setDuration(duration);
animator.start();
}
/**
* start horizontal turn animate delayed
*/
public void startHorizontalAnimateDelayed(final long delayed, final long duration) {
new Thread(new Runnable() {
@Override public void run() {
try {
Thread.sleep(delayed);
} catch (InterruptedException e) {
e.printStackTrace();
}
post(new Runnable() {
@Override public void run() {
startHorizontalAnimate(duration);
}
});
}
}).start();
}
/**
* start vertical turn animate
*/
public void startVerticalAnimate(long duration) {
final ValueAnimator animator = ValueAnimator.ofFloat(-180f, 0f);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
mDegreeX = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override public void onAnimationStart(Animator animation) {
}
@Override public void onAnimationEnd(Animator animation) {
mDegreeX = 0;
animator.removeAllUpdateListeners();
}
@Override public void onAnimationCancel(Animator animation) {
}
@Override public void onAnimationRepeat(Animator animation) {
}
});
animator.setDuration(duration);
animator.start();
}
/**
* start vertical turn animate delayed
*/
public void startVerticalAnimateDelayed(final long delayed, final long duration) {
new Thread(new Runnable() {
@Override public void run() {
try {
Thread.sleep(delayed);
} catch (InterruptedException e) {
e.printStackTrace();
}
post(new Runnable() {
@Override public void run() {
startVerticalAnimate(duration);
}
});
}
}).start();
}
/**
* start loop animate
*/
public void startHorizontalAnimate() {
isPlaying = true;
invalidate();
}
/**
* stop the loop animate
*/
public void stopAnimate() {
isPlaying = false;
mLoopAnimateY = 0;
invalidate();
}
}
2.Activity中使用
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.wingsofts.threedlayout.ThreeDLayout;
import java.util.ArrayList;
import java.util.List;
public class WeatherActivity extends AppCompatActivity {
RecyclerView recyclerView;
private Adapter adapter;
private boolean flag = true;
List<String> list = new ArrayList<>();
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
list.add("周日");
list.add("周一");
list.add("周二");
list.add("周三");
list.add("周四");
list.add("周五");
list.add("周六");
adapter = new Adapter(list);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
ThreeDLayout layout = (ThreeDLayout) findViewById(R.id.td_header);
layout.setTouchable(true);
layout.setTouchMode(ThreeDLayout.MODE_BOTH_X_Y);
}
public void onClick(View v){
ThreeDLayout layout = (ThreeDLayout) findViewById(R.id.td_header);
if(!flag){
flag = true;
((TextView)findViewById(R.id.textView)).setText("72℉");
}else {
flag = false;
((TextView)findViewById(R.id.textView)).setText("30℃");
}
layout.startHorizontalAnimate(1000);
for(int i = 0;i<list.size();i++){
((ThreeDLayout)recyclerView.getChildAt(i)).startHorizontalAnimateDelayed(100*i,1000);
}
}
class Adapter extends RecyclerView.Adapter<Adapter.MyViewHolder> {
List<String> list;
public Adapter(List<String> list) {
this.list = list;
}
@Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MyViewHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_main, parent, false));
}
@Override public void onBindViewHolder(Adapter.MyViewHolder holder, int position) {
holder.textView.setText(list.get(position));
}
@Override public int getItemCount() {
return list.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView;
TextView temperatureTxt;
MyViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.textView);
temperatureTxt = (TextView) itemView.findViewById(R.id.tv_temperature);
}
}
}
}
3.activity_weather.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/threeDLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.wingsofts.myapplication.WeatherActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.wingsofts.threedlayout.ThreeDLayout
android:id="@+id/td_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="30℃"
android:textColor="#fff"
android:textSize="80sp" />
</com.wingsofts.threedlayout.ThreeDLayout>
<com.wingsofts.myapplication.MyRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="START" />
</LinearLayout>
</LinearLayout>
item_main.xml
<?xml version="1.0" encoding="utf-8"?>
<com.wingsofts.threedlayout.ThreeDLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/colorPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:background="@color/colorPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
>
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="left"
android:textColor="#fff"
android:textSize="16sp"
/>
<TextView
android:id="@+id/tv_temperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="30℃"
android:textColor="#fff"
android:textSize="16sp"
/>
</LinearLayout>
</com.wingsofts.threedlayout.ThreeDLayout>
上一篇: 一个炫字都不够??!!!手把手带你打造3D自定义view
下一篇: scrapy 爬虫之简单使用