Android自定義View實(shí)現(xiàn)3D翻轉(zhuǎn)效果

目錄

效果展示

關(guān)鍵知識(shí)點(diǎn)

這個(gè)3D翻轉(zhuǎn)效果的核心其實(shí)就是Rotate3DAnimation這個(gè)自定義的Animation類

public class Rotate3DAnimation extends Animation {
    private int mCenterX,mCenterY;
    private Camera mCamera;
    private float mFromDegrees,mToDegrees;
    private AnimationUpdateListener updateListener;
    private int mWidth;

    public AnimationUpdateListener getUpdateListener() {
        return updateListener;
    }

    public void setUpdateListener(AnimationUpdateListener updateListener) {
        this.updateListener = updateListener;
    }

    public Rotate3DAnimation(float mFromDegrees, float mToDegrees) {
        this.mFromDegrees = mFromDegrees;
        this.mToDegrees = mToDegrees;
    }

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        mWidth = width;
        mCenterX = width / 2;
        mCenterY = height / 2;
        mCamera = new Camera();
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        float degress = mFromDegrees + (interpolatedTime *  (mToDegrees - mFromDegrees));

        if(updateListener != null){
            updateListener.onProgressUpdate(interpolatedTime,degress);
        }
        Matrix matrix = t.getMatrix();
        mCamera.save();
        //讓旋轉(zhuǎn)90度的時(shí)候不顯的太大
        if(interpolatedTime >= 0.5){
            mCamera.translate(0,0,(Math.abs(interpolatedTime - 1) / 0.5f) * mWidth / 2);
        }else {
            mCamera.translate(0,0,(interpolatedTime / 0.5f) * mWidth / 2);
        }
        //圖形繞Y軸旋轉(zhuǎn)
        mCamera.rotateY(degress);
        mCamera.getMatrix(matrix);
        //將原點(diǎn)移動(dòng)到中心處
        matrix.preTranslate(-mCenterX,-mCenterY);
        matrix.postTranslate(mCenterX,mCenterY);
        mCamera.restore();

        super.applyTransformation(interpolatedTime, t);
    }

    /**
     * 動(dòng)畫(huà)更新的回調(diào)
     */
    public interface AnimationUpdateListener{
        /**
         * 進(jìn)度回調(diào)
         */
        void onProgressUpdate(float progress,float value);
    }
}

我們這里主要是通過(guò)對(duì)android.graphics.Camera的操作來(lái)實(shí)現(xiàn)3D的變化,Camera的坐標(biāo)系為三維左手坐標(biāo)系双吆,因此我們可以通過(guò)操作它來(lái)實(shí)現(xiàn)一些3D的效果

接下來(lái)我對(duì)各段代碼進(jìn)行詳細(xì)說(shuō)明觉痛。

下面這段代碼是為了防止當(dāng)圖像旋轉(zhuǎn)到90度的時(shí)候,圖像的側(cè)面剛好朝著我們導(dǎo)致看起來(lái)過(guò)大的問(wèn)題眷篇,因此我們需要將圖像沿著Z軸移動(dòng)一下例诀,就相當(dāng)于一輛車從你身后往前開(kāi)你會(huì)感覺(jué)車越來(lái)越小一個(gè)道理朽肥。

 //讓旋轉(zhuǎn)90度的時(shí)候不顯的太大
        if(interpolatedTime >= 0.5){
            mCamera.translate(0,0,(Math.abs(interpolatedTime - 1) / 0.5f) * mWidth / 2);
        }else {
            mCamera.translate(0,0,(interpolatedTime / 0.5f) * mWidth / 2);
        }
不使用此段代碼
使用此段代碼后

下面這段代碼是實(shí)現(xiàn)了圖像旋轉(zhuǎn)

//圖形繞Y軸旋轉(zhuǎn)
        mCamera.rotateY(degress);

下面這段代碼是為了將原點(diǎn)移動(dòng)到圖像的中心點(diǎn)

 //將原點(diǎn)移動(dòng)到中心處
        matrix.preTranslate(-mCenterX,-mCenterY);
        matrix.postTranslate(mCenterX,mCenterY);

如果不將圖像移動(dòng)到中心點(diǎn)則圖像會(huì)沿著圖像的左邊旋轉(zhuǎn)如下

這里還有一點(diǎn)要注意的是菇民,我們需要將旋轉(zhuǎn)后展示的頁(yè)面(效果圖的反面)提前先反轉(zhuǎn)尽楔,這樣在旋轉(zhuǎn)后展示反面的時(shí)候就不會(huì)出現(xiàn)展示鏡像的問(wèn)題了,這里我通過(guò)繼承FrameLayout并在dispatchDraw增加如下邏輯來(lái)實(shí)現(xiàn)的

@Override
    protected void dispatchDraw(Canvas canvas) {
        //將整個(gè)頁(yè)面水平翻轉(zhuǎn)第练,目的是為了抵消外布局翻轉(zhuǎn)后的左右倒置現(xiàn)象
        mCamera.save();
        canvas.save();
        //鏡像反轉(zhuǎn)
        mCamera.rotateY(180);
        Matrix matrix = new Matrix();
        mCamera.getMatrix(matrix);
        matrix.preTranslate(-getWidth()/2,-getHeight()/2);
        matrix.postTranslate(getWidth()/2,getHeight()/2);
        canvas.setMatrix(matrix);
        mCamera.restore();
        super.dispatchDraw(canvas);
        canvas.restore();
    }

3D翻轉(zhuǎn)控件的使用方法

Rotate3DLayout內(nèi)必需包含DefaultLayout(默認(rèn)展示的頁(yè)面)和ReverseLayout(翻轉(zhuǎn)后展示的頁(yè)面)阔馋,然后以在DefaultLayout和ReverseLayout中進(jìn)行自己的布局即可

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/bt_rotate"
        android:text="3D旋轉(zhuǎn)"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <com.example.rotate3dlayout.widget.Rotate3DLayout
        android:id="@+id/rl_rotate"
        android:background="@drawable/shape_bg"
        android:padding="10dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="20dp"
        android:layout_width="300dp"
        android:layout_height="400dp">
        <com.example.rotate3dlayout.widget.DefaultLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <LinearLayout
                android:orientation="vertical"
                android:layout_gravity="center"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <TextView
                    android:text="我是正面"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>
                <Button
                    android:text="我是正面"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"/>
                <EditText
                    android:hint="我是正面"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"/>
            </LinearLayout>
        </com.example.rotate3dlayout.widget.DefaultLayout>
        <com.example.rotate3dlayout.widget.ReverseLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <LinearLayout
                android:orientation="vertical"
                android:layout_gravity="center"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <TextView
                    android:text="我是反面"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>
                <Button
                    android:text="我是反面"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"/>
                <EditText
                    android:hint="我是反面"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"/>
            </LinearLayout>
        </com.example.rotate3dlayout.widget.ReverseLayout>
    </com.example.rotate3dlayout.widget.Rotate3DLayout>
</LinearLayout>

通過(guò)rotate3D方法即可實(shí)現(xiàn)翻轉(zhuǎn)

public class MainActivity extends AppCompatActivity {
    private Button btRotate;
    private Rotate3DLayout rlRotate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btRotate = (Button) findViewById(R.id.bt_rotate);
        rlRotate = (Rotate3DLayout) findViewById(R.id.rl_rotate);
        //點(diǎn)擊后翻轉(zhuǎn)
        btRotate.setOnClickListener(v->rlRotate.rotate3D());
    }
}

案例源碼

https://gitee.com/itfitness/rotate3d-layout

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市娇掏,隨后出現(xiàn)的幾起案子呕寝,更是在濱河造成了極大的恐慌,老刑警劉巖婴梧,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件下梢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡塞蹭,警方通過(guò)查閱死者的電腦和手機(jī)孽江,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浮还,“玉大人竟坛,你說(shuō)我怎么就攤上這事【啵” “怎么了担汤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)洼冻。 經(jīng)常有香客問(wèn)我崭歧,道長(zhǎng),這世上最難降的妖魔是什么撞牢? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任率碾,我火速辦了婚禮,結(jié)果婚禮上屋彪,老公的妹妹穿的比我還像新娘所宰。我一直安慰自己,他們只是感情好畜挥,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布仔粥。 她就那樣靜靜地躺著,像睡著了一般蟹但。 火紅的嫁衣襯著肌膚如雪躯泰。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天华糖,我揣著相機(jī)與錄音麦向,去河邊找鬼。 笑死客叉,一個(gè)胖子當(dāng)著我的面吹牛诵竭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播兼搏,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼卵慰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了向族?” 一聲冷哼從身側(cè)響起呵燕,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎件相,沒(méi)想到半個(gè)月后再扭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡夜矗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年泛范,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片紊撕。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡罢荡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情区赵,我是刑警寧澤惭缰,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站笼才,受9級(jí)特大地震影響漱受,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜骡送,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一昂羡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧摔踱,春花似錦虐先、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至膀息,卻和暖如春般眉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背潜支。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工甸赃, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冗酿。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓埠对,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親裁替。 傳聞我的和親對(duì)象是個(gè)殘疾皇子项玛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容