在很久很久以前,寫了一篇自定義3d view的部落格。但是隻是講了如何實現,實現起來還是比較耗時,所以本著平易近人的心態,把他封裝成了一個ViewGroup,只需要在你的view或者佈局外面包裹一層ThreeDLayout 即可實現3D效果(畢竟:沒有什麼比拿來就能用更爽的事情了!!)。本文同步自博主的私人部落格wing的地方酒館
ThreeDLayout的專案地址:github.com/githubwing/…
效果預覽
3D觸控效果,旋轉效果,和用旋轉效果實現的特效
如何匯入ThreeDLayout
方式一
修改你的gradle檔案
allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
}
dependencies {
compile 'com.github.githubwing:ThreeDLayout:1.0.0'
}複製程式碼
方式二
將專案地址依賴庫 :threedlayout資料夾下ThreeDLayout.java拷貝至你的專案中,即可使用。
如何使用
以Demo中天氣Activity為例。
在xml中加入一個TextView,來顯示大溫度,下面一個RecyclerView,來顯示每天的溫度。
<LinearLayout
android:orientation="vertical"
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"
tools:context="com.wingsofts.myapplication.WeatherActivity"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/textView"
android:text="30℃"
android:textColor="#fff"
android:gravity="center"
android:textSize="80sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<com.wingsofts.myapplication.MyRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
</LinearLayout>複製程式碼
這就是一個最基本的介面實現。如何讓大溫度顯示旋轉起來呢?只需要用ThreeDlayout將其包裹。
<com.wingsofts.threedlayout.ThreeDLayout
android:background="@color/colorPrimary"
android:id="@+id/td_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/textView"
android:text="30℃"
android:textColor="#fff"
android:gravity="center"
android:textSize="80sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.wingsofts.threedlayout.ThreeDLayout>複製程式碼
在程式碼中獲取到該layout,並且設定觸控模式,即可實現:
ThreeDLayout layout = (ThreeDLayout) findViewById(R.id.td_header);
//開啟觸控模式
layout.setTouchable(true);
//設定模式為X,Y軸旋轉
layout.setTouchMode(ThreeDLayout.MODE_BOTH_X_Y);複製程式碼
接下來講解item動畫實現,可以看到其實item是一個接一個延遲旋轉,在ThreeDLayout中提供了翻轉動畫的方法:
//開啟水平翻轉動畫
startHorizontalAnimate(long duration)
//延遲開啟水平翻轉動畫
startHorizontalAnimate(long duration,long delayed)複製程式碼
所以Item動畫其實是一個for迴圈,讓他們依次執行動畫即可~~(當然item要使用ThreeDLayout包裹):
for(int i = 0;i<list.size();i++){
((ThreeDLayout)recyclerView.getChildAt(i)).startHorizontalAnimateDelayed(100*i,1000);
}複製程式碼
到這裡,ThreeDLayout的使用已經介紹完了,是不是很簡單呢。如果你感興趣,可以繼續往下閱讀,會介紹ThreeDLayout是如何實現的。
ThreeDLayout如何實現
在很久的一篇部落格裡,我介紹瞭如何實現一個3Dview,這裡就不重複講解。手把手帶你擼一個3D view
這裡主要講解如何把每次都要寫的程式碼封裝起來。 我的初始思路就是直接包裹成一個ViewGroup,重寫onDraw()方法即可。= = 沒錯就是這麼簡單。
所以我把之前3D view的程式碼搬運過來了,然後重寫了一下onDraw()。
@Override protected void onDraw(Canvas canvas) {
mMatrix.reset();
mCamera.save();
mCamera.getMatrix(mMatrix);
mCamera.restore();
mMatrix.preTranslate(-mCenterX, -mCenterY);
mMatrix.postTranslate(mCenterX, mCenterY);
canvas.concat(mMatrix);
super.onDraw(canvas);
}複製程式碼
大概是這樣就能完成3D的效果了,但是執行起來沒鳥用。因為viewgroup的onDraw()一般是不會呼叫的。怎麼解決呢?其實讓viewgroup參與draw的過程就好啦,於是我在構造器裡給他新增了個背景色。有了背景色他就會呼叫onDraw了。
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"));
}
mCamera = new Camera();
mMatrix = new Matrix();
}複製程式碼
不過事情沒有這麼簡單,還要解決測量問題,於是這裡我就取巧,讓ThreeDLayout只有一個子view,這樣就可以把大小設定成子view的大小,免去測量的過程,所以onMeasure()是這樣的:
@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());
}複製程式碼
為了提供不同的需求,所以擴充套件一下,使用者可以自己設定是否開啟觸控模式,並且可以設定X,Y,所以在onDraw()裡進行一些判斷:
if (mMode == MODE_Y || mMode == MODE_BOTH_X_Y) {
mCamera.rotateX(mCanvasRotateX);
}
if (mMode == MODE_X || mMode == MODE_BOTH_X_Y) {
mCamera.rotateY(mCanvasRotateY);
}複製程式碼
現在一個ThreeDLayout就完成了。可是為了讓他更好用呢,要新增一個動畫效果,就是水平翻轉動畫,這樣實用性更高,就可以實現天氣Activity類似效果。所以在onDraw()裡要多加一層旋轉角度控制.
@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);
}複製程式碼
然後提供一個動畫開始的方法,順便當動畫完成的時候,使degree變為0,這樣就會處於不翻轉狀態:
public void startHorizontalAnimate(long duration){
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();
}複製程式碼
然後再提供一個延遲動畫的方法,內部開一個執行緒計時,然後去執行動畫方法即可:
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();
}複製程式碼
好啦,大功告成,以上就是ThreeDLayout的實現啦,如果你覺得效果比較cool,或者該控制元件比較實用,歡迎star一下~ 如果你喜歡我的部落格,歡迎評論以及關注我~
ThreeDLayout的專案地址:github.com/githubwing/…
如果你是Android開發者,那麼你還可以來 wing的酒館:425983695
來分享你的開發經驗哦