5秒讓你的View變3D,ThreeDLayout使用和實現

在很久很久以前,寫了一篇自定義3d view的博客。但是隻是講了如何實現,實現起來還是比較耗時,所以本着平易近人的心態,把他封裝成了一個ViewGroup,只需要在你的view或者佈局外面包裹一層ThreeDLayout 即可實現3D效果(畢竟:沒有什麼比拿來直接用更爽的時期)。本文同步自博主的私人博客wing的地方酒館

ThreeDLayout的項目地址:https://github.com/githubwing/ThreeDLayout


效果預覽

3D觸摸效果,旋轉效果,和用旋轉效果實現的特效

這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述


如何導入ThreeDLayout

方式一

在gralde下加入compile('com.wingsofts.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的項目地址:https://github.com/githubwing/ThreeDLayout

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章