【Android】底部彈出的DialogFragment,支持從右向左push二級頁面,自帶彈出時摺疊動畫

前言

好久不見,今天來分享一個可能大家會用到的工具,然後打算持續集成開發優化,爲大家做一個有人管理的第三方庫,做到好用實用等等等等~哈哈哈,先看看效果圖~
這裏寫圖片描述

簡介

嗯~沒錯的,就是這個從底部彈出的DialogFragment,然後我叫它是BottomSheet,當然通過效果圖也看出來了,不僅僅是能從底部向上彈出,還能將Activity整體縮放,實現一個凹下去的效果,當然動畫效果後期可以修改優化,到時候也可以大家自己去實現。

BottomSheet 其本質是DialogFragment,實現了從底部彈出的效果,並且自帶將Activity縮放的一個動畫效果(後期會更加豐富),可以實現在BottomSheet 中push(Fragment)的形式從右向做添加新的Fragment

主要功能

1.builder模式的超簡單使用方式
2.自帶標題,所有事件可控,所有標題內容可定製
3.通過push的方式添加Fragment,並且自帶從右到左的push效果和從左到右的popUp效果。
4.BottomSheet 彈出時,自帶外層Activity動畫,可以定製。
5.目前更多功能還在測試開發階段

項目地址:https://github.com/Blincheng/BottomSheet

集成導入(gradle)

1.Add the JitPack repository to your build file .Add it in your root build.gradle at the end of repositories:

allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

2.Add the dependency

dependencies {
            compile 'com.github.Blincheng:BottomSheet:v0.3.2'
    }

其餘集成方式請看:https://jitpack.io/#Blincheng/BottomSheet/v0.3.2

使用

基本使用

BottomSheetDialogInterface builder = new BottomSheetSettingsBuilder(MainActivity.this).build();
        builder.show();

注意:基本原則就是設置好所有的參數後build,最後再show();

push一個頁面

builder.push(new FirstFragment(),new BottomSheetTitleSetting().setTitle("第一個標題"));

注意:push(*)原則上應該在show之後調用,當然先push第一個頁面,再show()在使用上也沒什麼問題。建議在show()之後push。我們來看看參數,void push(Fragment fragment, BottomSheetTitleSetting setting);第一個是頁面的Fragment沒疑問,然後第二個是一個BottomSheetTitleSetting,也就是說每個Fragment對應都有一個BottomSheetTitleSetting 來設置標題的內容,具體看下面的標題內容設置

返回一個頁面

builder.popUp();

說明:注意當一個頁面都沒push過的話調用是無效的,如果只有一個頁面,直接調用會關閉BottomSheet,如果有2個以上,則會正常從左向右popUp頁面。

設置外層Activity動畫的開關

builder.setOpenWindowShrinkAnim(true)//設置最外層動畫是否打開,默認開

設置BottomSheet的高度(參數意義是佔屏幕總高度的百分比,默認0.5)

builder.setContainerHeight(0.5f)

設置BottomSheet顯示後的回調

builder.addOnShowListeners(new DialogInterface.OnShowListener() {
                            @Override
                            public void onShow(DialogInterface dialog) {
                                //當打開Dialog後回調
                            }
                        })

注意:建議push第一個頁面的push在這個

設置BottomSheet關閉後的回調

builder.setOnDismissListener(new BottomSheetDismissInterface() {
                        @Override
                        public void dismiss(DialogInterface dialog) {
                            //當關閉Dialog後回調
                        }
                    })

設置標題欄等事件的基本回調

builder.setBottomSheetEventCallBack(new BottomSheetEventCallBack() {
                        @Override
                        public void onLeftClicked(BottomSheetDialogInterface dialogInterface, int pageIndex) {
                            //標題欄左邊按鈕點擊回調
                        }

                        @Override
                        public void onRightClicked(BottomSheetDialogInterface dialogInterface, int pageIndex) {
                            //標題欄右邊按鈕點擊回調
                        }
                        @Override
                        public void onSupernatantClick(BottomSheetDialogInterface dialogInterface, int pageIndex) {
                            //點擊空白部分回調
                        }
                    })

標題內容設置

首先要做的就是拿到這個BottomSheetTitleSetting setting = new BottomSheetTitleSetting();

設置標題內容

setting.setTitle("標題內容");

注意是參數類型是CharSequence,也就是說TextView支持的,這邊都支持。比如可以這樣

setting.setTitle(BottomSheetTitleSetting.getSpannableString("這是一個兩行的標題",
                                                "可以用SpannableString來助攻副標題哦","#000000","#808080",46,40))

效果就會變成這樣
這裏寫圖片描述

設置是否隱藏標題

setting.setTitleVisible(true);

設置標題的左右按鈕的顯示和隱藏

setting.setTitleButtonVisible(true,true);

設置標題左右文本按鈕的顯示和隱藏

setting.setLeftTextVisible(false);
setting.setRightTextVisible(false);

說明:默認文本按鈕都是隱藏的

其餘的接口要的也有加了 ,如果真有其他特殊需求建議clone,然後我也會慢慢完善的,有任何問題歡迎大家及時提出來,現在正在測試階段,希望大家一起來把這個事情做好。

實現過程

首先還是先感謝一下我們的雪晨帥哥(其實是我現在的老大,哈哈哈),他先是寫了一個類似的控件在我們的官方項目中,然後經過很多波折,被N個開發同學改來改去。然後我就把自己的使用感覺結合自己的想法,重新開發了一個開源框架分享給大家。
廢話不多說,我們繼續。既然現在很多項目中都用到類似的效果,那我們爲何不封裝一下,讓大家用的更加開心呢?實現思路:
1.用Dialog還是DialogFragment?
2.標題欄要封裝嗎?
3.push的動畫是怎麼實現好呢?
4.彈出時的摺疊動畫怎麼實現(實現代碼真的很少,但是路程很艱辛)
5.封裝後怎麼使用才方便?

好,接下來我們就根據我上面的幾個問題來一一解答吧。

用Dialog還是DialogFragment?

其實很早很早Google就推薦大家用DialogFragment了,的確使用上很方便,android 3.0時被引入,是一種特殊的Fragment,用於在Activity的內容之上展示一個模態的對話框。如果對於DialogFragment使用不熟悉的小夥伴可以看看鴻洋的Android 官方推薦 : DialogFragment 創建對話框 相信大家都很喜歡他,哈哈哈~
這邊有點特別的是我們不在DialogFragment中去創建對應的佈局,而是通過Builder的形式來創建,所以,我們直接創建一個方法來設置佈局:

public void setContentView(View contentView) {
        this.contentView = contentView;
        containerHeight = contentView.findViewById(R.id.sheet_viewpager_container).getLayoutParams().height;
        contentView.findViewById(R.id.sheet_background).setOnClickListener(this);
        contentView.findViewById(R.id.sheet_left_btn).setOnClickListener(this);
        contentView.findViewById(R.id.sheet_right_btn).setOnClickListener(this);
        contentView.findViewById(R.id.sheet_left_text).setOnClickListener(this);
        contentView.findViewById(R.id.sheet_right_text).setOnClickListener(this);
        mViewPager = (ViewPager) contentView.findViewById(R.id.sheet_viewpager_container);
        mViewPager.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return true;
            }
        });
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                currentIndex = position;
                initTitle(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
        setViewPagerScroller(mViewPager);
        title_layout = contentView.findViewById(R.id.sheet_title);
        title_tv = (TextView) contentView.findViewById(R.id.sheet_title_text);
        title_left_tv = (TextView) contentView.findViewById(R.id.sheet_left_text);
        title_right_tv = (TextView) contentView.findViewById(R.id.sheet_right_text);
        title_left_btn = (ImageView) contentView.findViewById(R.id.sheet_left_btn);
        title_right_btn = (ImageView) contentView.findViewById(R.id.sheet_right_btn);
        title_line = contentView.findViewById(R.id.sheet_title_line);
    }

我們在這裏才把所有要的東西找到,並且初始化,因爲我們的Builder設計模式會有很多的參數可能需要變化,如果直接在DialogFragment中創建的話,很多因爲可能還不確定。

標題欄要封裝嗎?

最後我想了想,大多數還是需要標題的,所以還是一起封裝了,支持左邊右邊文本,ICON等,都有接口可以設置。直接看佈局文件吧:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <View
        android:id="@+id/sheet_background"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:alpha="0.3"
        android:background="#000000"/>
    <android.support.v4.view.ViewPager
        android:id="@+id/sheet_viewpager_container"
        android:background="@android:color/white"
        android:layout_width="match_parent"
        android:layout_alignParentBottom="true"
        android:layout_height="240dp"/>
    <View
        android:id="@+id/sheet_title_line"
        android:layout_above="@id/sheet_viewpager_container"
        android:layout_width="match_parent"
        android:layout_height="0.2dp"
        android:background="@color/bottom_sheet_DDDDDD"/>
    <RelativeLayout
        android:id="@+id/sheet_title"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@android:color/white"
        android:layout_above="@id/sheet_title_line"
        android:gravity="center_vertical">

        <ImageView
            android:id="@+id/sheet_left_btn"
            android:layout_width="50dp"
            android:layout_height="match_parent"
            android:layout_alignParentLeft="true"
            android:scaleType="centerInside"
            android:src="@mipmap/icon_back" />

        <ImageView
            android:id="@+id/sheet_right_btn"
            android:layout_width="50dp"
            android:layout_height="match_parent"
            android:scaleType="centerInside"
            android:src="@drawable/bottom_sheet_right_back"
            android:layout_alignParentRight="true" />
        <TextView
            android:id="@+id/sheet_right_text"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:paddingRight="10dp"
            android:paddingLeft="10dp"
            android:layout_centerVertical="true"
            android:visibility="gone"
            android:text="@string/bottom_sheet_right_close_text"
            android:gravity="center"
            android:textColor="@color/bottom_sheet_808080"
            android:layout_alignParentRight="true"/>
        <TextView
            android:id="@+id/sheet_left_text"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:paddingRight="10dp"
            android:paddingLeft="10dp"
            android:visibility="gone"
            android:layout_centerVertical="true"
            android:text="@string/bottom_sheet_left_close_text"
            android:gravity="center"
            android:textColor="@color/bottom_sheet_808080"
            android:layout_alignParentLeft="true"/>
        <TextView
            android:id="@+id/sheet_title_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textSize="@dimen/text_size_46px"
            android:textColor="#000000"
            android:gravity="center"
            android:text="@string/app_name"/>
    </RelativeLayout>
</RelativeLayout>

瀏覽一下縮略圖哈
這裏寫圖片描述
就是一個標題,一個ViewPager,所以說,下面一個問題順便也解答了,肯定還是用ViewPager來實現push 的動效了。

封裝後怎麼使用才方便?

其實這個纔是我要討論的問題,也要深入研究,因爲一個好的調用方式會非常巧妙的實現某種功能或者某個效果。然後BottomSheetSettingsBuilder閃亮登場,這是一個builder,請原諒我new的這種方式創建(尷尬臉),這邊既然是buidler,那麼說明要設置的所有東西都可以在這邊設置。然後問題來了,那我們的標題的事件怎麼設置呢?標題樣式呢?如果我push第二個頁面進來,那麼標題是不是也要跟着變化,所以最後我將標題的控制放在了push接口裏面public void push(Fragment fragment, BottomSheetTitleSetting setting)
也就是說,builder管理了所有的事情,包括標題的點擊事件,空白部分的點擊事件,唯一不在乎的事情就是標題的樣式是在BottomSheetDialogInterface中完成的。

那麼怎麼開始寫呢?一般來說我們應該是先寫一個接口:

public interface BottomSheetDialogInterface {
    public void cancel();
    public void push(Fragment fragment, BottomSheetTitleSetting setting);
    public void popUp();
    public void show();
}

這個接口呢應該包含所有那些重要的事情,必須要去實現的,並且圍繞着幾個方法去管理整個過程。那當然,自然而然的是BottomSheetDialogFragment去實現它。我們分別來看看這4個接口方法中都做了什麼。
1.最簡單的cancel(),當然就是一個取消動畫,然後把要做的其他比如說狀態啊什麼的都重置。

@Override
    public void cancel() {
        if(!isHidden()&&isShow) {
            isShow = false;
            contentView.findViewById(R.id.sheet_background).setAlpha(0.0f);
            contentView.findViewById(R.id.sheet_title).startAnimation(getSlideDownAnimation());
            contentView.findViewById(R.id.sheet_title_line).startAnimation(getSlideDownAnimation());
            TranslateAnimation s = getSlideDownAnimation();
            s.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {}
                @Override
                public void onAnimationEnd(Animation animation) {dismiss();}
                @Override
                public void onAnimationRepeat(Animation animation) {}
            });
            contentView.findViewById(R.id.sheet_viewpager_container).startAnimation(s);
        }
    }

2.popUp()這個接口方法其實是用在有多個頁面的時候,看內容也很簡單,當fragments只有1個的時候直接調用cancel方法,如果2個時候就remove一個最後的fragment。然後刷新viewPager,並且移動到前一個fragment。

@Override
    public void popUp() {
        if(fragments.size() == 0)
            return;
        if(fragments.size() == 1){
            cancel();
            return;
        }
        if(fragments.size() > 1){
            bottomSheetTitleSettings.remove(bottomSheetTitleSettings.size()-1);
            fragments.remove(fragments.size()-1);
        }
        adapter.notifyDataSetChanged();
        mViewPager.setCurrentItem(fragments.size()-1);
    }

3.show()方法當然就是展示我們的BottomSheet啦~

@Override
    public void show() {
        if(mActivity != null&& mActivity instanceof FragmentActivity){
            if(isOpenWindowShrinkAnim){
                AnimationUtils.startZoomAnimation(mActivity,true);
            }
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    BottomSheetDialogFragment.super.show(mActivity.getSupportFragmentManager(), Config.BOTTOM_SHEET_DIALOG_FRAGMENT);
                    AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);
                    animation.setDuration(500);//設置動畫持續時間
                    animation.setInterpolator(new LinearInterpolator());
                    contentView.findViewById(R.id.sheet_background).startAnimation(animation);
                    contentView.findViewById(R.id.sheet_title).startAnimation(getSlideUpAnimation());
                    contentView.findViewById(R.id.sheet_title_line).startAnimation(getSlideUpAnimation());
                    contentView.findViewById(R.id.sheet_viewpager_container).startAnimation(getSlideUpAnimation());
                }
            },200);
        }else{
            throw new RuntimeException("Activity must be instanceof support.v4.app.FragmentActivity");
        }
    }

其實可以看到show()和cancel()中調用了一個外層動畫是吧~咱們後面再好好講講這個3D動畫的實現過程。
4.push()接口方法

@Override
    public void push(Fragment fragment, BottomSheetTitleSetting setting) {
        if(isShow){
            if(fragments.size() == 0){
                mViewPager.setAdapter(adapter = new BottomViewPagerAdapter(getChildFragmentManager(),fragments));
            }
            fragments.add(fragment);
            bottomSheetTitleSettings.add(setting);
            adapter.notifyDataSetChanged();
            mViewPager.setCurrentItem(fragments.size()-1);
            initTitle(fragments.size()-1);
        }else{
            fragments.add(fragment);
            bottomSheetTitleSettings.add(setting);
        }
    }

然後看了這4個方法,其實大家應該都差不多瞭解了整個BottomSheetDialogFragment內容,其實就是有一個ArrayList<Fragment> fragments還有一個ArrayList<BottomSheetTitleSetting> bottomSheetTitleSettings 然後一個ViewPager來展示這些Fragment。當然爲了不讓ViewPager可以滑動,我們把ViewPager的滑動事件處理下:

 mViewPager.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return true;
            }
        });

貌似這是最簡單的 處理方式了吧。然後我有點強迫症,ViewPager的滾動速度太快了,我就還想這麼做,讓他慢一點:

private void setViewPagerScroller(ViewPager viewPager) {

        try {
            Field scrollerField = ViewPager.class.getDeclaredField("mScroller");
            scrollerField.setAccessible(true);
            Field interpolator = ViewPager.class.getDeclaredField("sInterpolator");
            interpolator.setAccessible(true);

            Scroller scroller = new Scroller(mActivity, (Interpolator) interpolator.get(null)) {
                @Override
                public void startScroll(int startX, int startY, int dx, int dy, int duration) {
                    // 這裏是關鍵,將duration變長或變短
                    super.startScroll(startX, startY, dx, dy, duration * 5);
                }
            };
            scrollerField.set(viewPager, scroller);
        } catch (NoSuchFieldException e) {
            // Do nothing.
        } catch (IllegalAccessException e) {
            // Do nothing.
        }
    }

說到這裏,其實大家也能猜到,BottomSheetSettingsBuilder這個其實就是相當於一箇中繼器,先把需要的東西設置到builder中,然後在最後build的時候把所有的東西都設置給BottomSheetDialogFragment,最後再調用show();

這邊有點細節,比如在我們的push(…)方法中

if(isShow){}else{
    fragments.add(fragment);
    bottomSheetTitleSettings.add(setting);
}

可以看到沒有顯示的時候其實沒有做其他的事情,也沒有刷新ViewPager。其實按理說我們應該先show()再push,甚至在View顯示的回調中去push()這樣保證所有的View都創建完成了再去刷新Viewp纔是最保險的。當然,這些問題肯定是在我們考慮之中的,所以當我們的onViewCreated創建後,我們去調用了

if(mViewPager != null&&fragments.size()>0){
            mViewPager.setAdapter(adapter = new BottomViewPagerAdapter(getChildFragmentManager(),fragments));
            adapter.notifyDataSetChanged();
            mViewPager.setCurrentItem(fragments.size()-1);
            initTitle(fragments.size()-1);
        }

也就是說其實BottomSheet在使用過程中根本不用去考慮哪個方法先後,想幹啥幹啥,一步走到底就行。(別和我說build()方法的先後。。。那我說不過你。。。肯定打得過你)

摺疊的3D動畫的實現過程

其實去看Tag,之前的動畫我就簡單謝啦一個縮放而已,雖然很流暢,但是的確缺少新奇點~還被同事嘲諷了~(委屈臉)先看看京東的效果啊(隨便點擊一個商詳選擇配送地址就能看到這個效果,然後我打開Android版本的京東…很尷尬,這是歧視嗎???竟然沒有!)

實現思路

1.先從一個View上面去實現,然後再看看怎麼實現我們的整個頁面
2.這肯定是個3D動畫了,有一個前後拉進的距離
3.多看看京東的效果(然後我點了N久的京東…)
4.封裝,優化

先實現ImageView的動畫吧

不多說,馬上動手新建一個工程,放一個大大的ImangeView在我們的MAinActivity,然後開始探索。

繼承Animation是我們的唯一出路

這動畫效果肯定只能是來繼承Animation了,那怎麼實現呢?然後最終找到了它!就是

android.graphics.Camera

這個相機並不是我們拍照用的相機,可以這麼理解,我們看View的時候,頭部在動,然後View其實也有不同的樣子,那麼這個Camera就是相當於我們手機的眼睛。我們適當移動它,那麼在我們的手機上展現出來的效果就是你們想看到的3D效果。
本次主要用到一下幾個方法:

mCamera.save();//保存當前Camera的狀態
mCamera.rotateX();//繞X軸旋轉
mCamera.rotateY();//繞Y軸旋轉
mCamera.rotateZ();//繞Z軸旋轉
mCamera.translate(x,y,z);//平移的方法,X/Y不多說,Z如果不爲0就是向前向後平移,如果z>0 就是向Z軸的正方向平移z個單位,其實對於我們的視覺來講就是縮小;如果z<0其實就是放大
mCamera.rotateX(x);//繞X軸旋轉x度

然後畫了一幅圖~大家領悟一下~
這裏寫圖片描述

其實座標系應該是這樣的,一開始手機是這樣放着的。也就是說其實你調用任何一個旋轉的方法,其實旋轉的中心都是手機的左上角,很尷尬的一個事情。

看看關鍵的點

@Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        mCamera.save();
        Matrix matrix = t.getMatrix();
        if(interpolatedTime < 0.5f){
            if(isOpen)//如果是打開狀態,則當前時段一直保持縮小
                mCamera.translate(0,0,10*SCALE_CHANGE*(1-0.5f));
            mCamera.rotateX(SCALE_CHANGE*interpolatedTime);
        }else{
            if(isOpen)//如果是打開狀態,從小變大
                mCamera.translate(0,0,10*SCALE_CHANGE*(1f-interpolatedTime));
            else//是關閉狀態,從大變小
                mCamera.translate(0,0,10*SCALE_CHANGE*(interpolatedTime-0.5f));
            mCamera.rotateX((1-interpolatedTime)*SCALE_CHANGE);
        }
        mCamera.getMatrix(matrix);
        mCamera.restore();
        matrix.preTranslate(-width/2, -height/2);
        matrix.postTranslate(width/2, height/2);
    }

其實核心代碼就真的只有這麼幾行~當然一開始我是各種調,各種領悟啊。。。
整個動畫過程其實無非就是這樣一個過程,先屏幕上半部分往裏面折,然後下面部分再往裏面折,同時在下面部分往裏面折的過程的同時整個View都往Z軸正方向平移(其實Camera是往Z軸-方向移動,想想你的眼睛移動後的效果),也就是說縮小。
然後放大動畫就是前半部分時間都是保持縮小的狀態,然後在後面部分的時候再放大回到原來的大小即可。

那麼我們用代碼來拆分一下(applyTransformation這個方法不懂的先自學習啊哈~):
1.先考慮縮小動畫:

我們先把整個動畫拆分成2個過程,也就是對半分。所以以interpolatedTime < 0.5f爲界限,
每次調用applyTransformation的時候,我們先保存一下mCamera.save();的狀態,主要是播放完一幀後將相機放回原處。
然後我們將View繞X軸旋轉SCALE_CHANGE*interpolatedTime度數,其實就是將View向裏面旋轉。但是請注意,看我上面的圖,其實這個時候旋轉時有效的,但是旋轉的中心是0點,並不是我們的View的中心,所以我們需要

matrix.preTranslate(-width/2, -height/2);
matrix.postTranslate(width/2, height/2);

來助攻,當然這兩個方法可以實現我們所謂的旋轉中心的改變,其實也沒變,只是將圖片以矩陣的形式進行操作,達到與旋轉中心改變一樣的效果罷了。preTranslate方法的作用是在旋轉之間先把圖片向上移動圖片高度的一半的距離,這樣圖片就關於x軸對稱了,然後再進行旋轉的變換,postTranslate方法是在變換之後再將圖片向下移動圖片高度的一半的距離也即是回到了原來的位置,這樣圖片顯示出來的結果就是對稱的了。原理也很簡單,旋轉中心還是(0,0),只不過我們移動圖片,這樣進行旋轉變換的時候就會得到對稱的結果了。
然後操作完後記得mCamera.restore();還原我們的攝像機。
然後當大於0.5f的時候,這個時候需要平移加旋轉,縮放其實就是延Z軸平移,如果說是縮小,那麼我們就像Z軸的正方向平移n個單位。一開始我們是繞X軸旋轉從0-0.5f,然後縮小就是從0.5-0嘍,那就直接(1f-interpolatedTime)。

2.處理放大動畫
當我們的interpolatedTime<0.5的時候,其實一直是屬於縮小狀態的,所以我們其實一直就是調用

mCamera.translate(0,0,10*SCALE_CHANGE*(1f-interpolatedTime));

旋轉其實你會發現,和縮小是一樣的,唯一不一樣的就是在後面半段0.5-1的時候,這個時候你需要逐漸放大我們的View。那就是說從0.5-0嘍mCamera.translate(0,0,10*SCALE_CHANGE*(1f-interpolatedTime));那就是這樣嘍。是不是搞定了,然後把所有代碼合併,最後就是現在這樣子了。其實我剛開始寫的時候,因爲對於Camera不熟悉,所以還是遇到挺多坑的。還有就是在處理完事情後一定要mCamera.restore();不然如果你設置了setFillAfter(true);你的動畫會非常可怕的~你可以試試。剛開始我肯定也是分爲2個動畫去寫,先寫縮小,再寫放大,然後再合併。(奸笑臉)

然後實現整個頁面的縮放動畫

其實剛開始我是直接拿外層ViewGroup去播放動畫,然後用在項目中你會發現項目中各種View的疊加,動畫播放的時候竟然會閃爍!好了,那就只能想其他辦法了。
通過屏幕截圖來獲取bitmap,然後把ImageView添加到我們的android.R.id.content佈局,最後實現全屏動畫的摺疊效果。

 private void initLayout(){
        setInterpolator(new AccelerateInterpolator());
        setFillAfter(true);
        setDuration(400);
        contentView = (ViewGroup)this.activity.findViewById(android.R.id.content);
        contentView.setDrawingCacheEnabled(true);
        if(contentView.findViewWithTag(TAG_FOR_PARENT) == null){
            parentView = new FrameLayout(this.activity);
            parentView.setTag(TAG_FOR_PARENT);
            parentView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,FrameLayout.LayoutParams.MATCH_PARENT));
            parentView.setBackgroundColor(Color.parseColor("#000000"));
            childView = new ImageView(activity);
            childView.setTag(TAG_FOR_CHILD);
            childView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
            parentView.addView(childView);
            contentView.addView(parentView);
        }else{
            parentView = (FrameLayout) contentView.findViewWithTag(TAG_FOR_PARENT);
            childView = (ImageView) parentView.findViewWithTag(TAG_FOR_CHILD);
        }
        parentView.setVisibility(View.GONE);
        contentView.destroyDrawingCache();//釋放緩存資源
        contentView.buildDrawingCache();
        childView.setImageBitmap(contentView.getDrawingCache());
    }

看代碼,還是我們的老方法~哈哈,真的好用,具體有興趣可以看看我的【Android】當關閉通知消息權限後無法顯示系統Toast的解決方案 這個Toast就是添加在最外層佈局的~
上面我創建了一個FrameLayout,然後設置成MATCH_PARENT,並設置背景顏色爲黑色,然後創建了一個ImageView,頁設置成MATCH_PARENT。並且都設置TAG,方式一個頁面添加多個的問題(EToast的經驗)
關鍵代碼就是這個:

contentView.setDrawingCacheEnabled(true);
contentView.destroyDrawingCache();//釋放緩存資源
contentView.buildDrawingCache();
childView.setImageBitmap(contentView.getDrawingCache());

其實上面短短4行代碼就把截屏拿下了哦~不用任何權限,並且是所有View都自帶的效果。唯一要注意的是爲此截圖的時候需要先釋放緩存資源,不然每次拿出來的Bitmap都是一樣的。

public void startAnimation() {
        parentView.setVisibility(View.VISIBLE);
        childView.startAnimation(this);
        if(isOpen){
            setAnimationListener(new AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    parentView.setVisibility(View.GONE);
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
        }
    }

最後創建一個startAnimation接口,然後調用對應啓動動畫的方法,處理好之後的事情即可。

總結

不知不覺都10點半了。。。我得趕緊回家~我家黑爺可要着急了。然後有啥問題的,歡迎大家給我留言討論,很樂意和大家探討哦~

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