android書(shū)籍打開(kāi)動(dòng)畫(huà)

嗯嗯,勉強(qiáng)可以接受已艰,效果圖如下


20190322_141518.gif

其實(shí)了主要就是對(duì)imageView進(jìn)行y軸的旋轉(zhuǎn)宫患,最早就是這個(gè)思路,可那個(gè)y軸旋轉(zhuǎn)到-45度左右圖片就不見(jiàn)了绿饵。
然后只能尋找其他辦法了。
想起以前對(duì)圖片進(jìn)行skew操作瓶颠,感覺(jué)效果差不多就試了下拟赊,就是上圖的效果,勉強(qiáng)可以粹淋。

上代碼即可

  1. CoverLocation實(shí)體類(lèi)
    存儲(chǔ)下封面的原始位置信息吸祟,方便傳遞到下個(gè)頁(yè)面使用
import java.io.Serializable
data class CoverLocation(var l:Int,var t:Int,var w:Int,var h:Int):Serializable{
    //記錄點(diǎn)擊的封面imageView的left,top桃移,right屋匕,bottom,width借杰,height
    var r=l+w
    var b=t+h
}
  1. 列表頁(yè)
    數(shù)據(jù)的加載啥的就自己處理了过吻,這里處理下點(diǎn)擊事件
    iv_cover:就是 那個(gè)封面imageView。我們獲取到他的location蔗衡,寬高疮装,傳遞給下個(gè)頁(yè)面。
                   holder.itemView.setOnClickListener {
                        val out = IntArray(2)
                        holder.getView<ImageView>(R.id.iv_cover).apply {
                            getLocationOnScreen(out)
                            var location = CoverLocation(out[0], out[1], this.width, this.height)
                            if (position % 2 == 0) {
                                activity?.startActivity(Intent(activity, ActivityBookContent::class.java).putExtra("info", location))
activity?.overridePendingTransition(0, 0);//防止有的手機(jī)默認(rèn)有頁(yè)面切換動(dòng)畫(huà)粘都,影響效果
                            } else {
                                FragmentBookContent.goFragmentContentPage(activity!!, R.id.container, location)
                            }
                        }
                    }
  1. 跳轉(zhuǎn)分2種測(cè)試
    3.1 跳轉(zhuǎn)到fragment
    建議用這種
    布局fragment_book_content.xml
    這里測(cè)試就簡(jiǎn)單弄個(gè)textView替代了廓推,實(shí)際中你可以是自定義布局,復(fù)雜布局翩隧,隨你啦
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#215201"
        android:clickable="true"
        android:gravity="center"
        android:text="no news is good news!\r\n everybody knows that"
        android:visibility="visible" />
</FrameLayout>

內(nèi)容頁(yè)Fragment
代碼也不多樊展,封裝了一個(gè)WithCoverContainerLayout來(lái)處理動(dòng)畫(huà),F(xiàn)ragment主要就是綁定下我們的內(nèi)容View
完事處理下后退的時(shí)候當(dāng)動(dòng)畫(huà)結(jié)束以后堆生,關(guān)閉這個(gè)Fragment

import android.graphics.BitmapFactory
import android.os.Bundle
import android.support.v4.app.FragmentActivity
import com.bumptech.glide.Glide
import com.charliesong.demo0327.R
import com.charliesong.demo0327.base.BaseFragment
import kotlinx.android.synthetic.main.fragment_book_content.*

class FragmentBookContent : BaseFragment() {
    override fun getLayoutID(): Int {
        return R.layout.fragment_book_content
    }

    var url = "http://user-gold-cdn.xitu.io/2017/12/13/1604f3080efffc24?imageslim"
    lateinit var coverLocation: CoverLocation
    lateinit var withCoverContainerLayout: WithCoverContainerLayout
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        initData()
        withCoverContainerLayout = WithCoverContainerLayout(activity).bindContent(tv_content, coverLocation)
                .apply {
                    backEndListener = {
                        if (isAdded) {
                            activity?.supportFragmentManager?.popBackStack()
                        }
                    }
                }
        withCoverContainerLayout.loadCoverView().apply {
            Glide.with(this).load(url).into(this)//這個(gè)url應(yīng)該是上個(gè)頁(yè)面?zhèn)鬟f過(guò)來(lái)的专缠。
//            setImageResource(R.drawable.book_default_cover)
        }
    }

    companion object {
        val tag = FragmentBookContent::class.java.name
        fun goFragmentContentPage(activity: FragmentActivity, containerId: Int, coverInfo: CoverLocation) {
            val fragment = activity.supportFragmentManager.findFragmentById(containerId)
            println("fragment==========$fragment")

            val old = activity.supportFragmentManager.findFragmentByTag(tag)
            if (old == null) {
                activity.supportFragmentManager.beginTransaction().add(containerId, FragmentBookContent().apply {
                    coverLocation = coverInfo
                }, tag).addToBackStack(tag)
                        .commitAllowingStateLoss()
            }
        }

        fun goBack(activity: FragmentActivity): Boolean {
            if (activity.supportFragmentManager.backStackEntryCount > 0) {
                println("back ========${activity.supportFragmentManager.backStackEntryCount}==${activity.supportFragmentManager.getBackStackEntryAt(0).name}")
                activity.supportFragmentManager.findFragmentByTag(tag)?.apply {
                    (this as FragmentBookContent).backAnima()
                    return true
                }
            }
            return false
        }
    }

    var isBacking = false
    fun backAnima() {
        if (isBacking) {
            return
        }
        isBacking = true
        withCoverContainerLayout
                .backAnimation()

    }
}

還得處理下activity里的后退事件

    override fun onBackPressed() {
        if (!FragmentBookContent.goBack(this)) {
            super.onBackPressed()
        }
    }

3.2 跳轉(zhuǎn)到activity
這種不太喜歡,咋說(shuō)了淑仆。大家知道頁(yè)面消失有個(gè)動(dòng)畫(huà)涝婉,會(huì)閃一下消失,還得處理蔗怠,麻煩墩弯,還得讓activity是透明背景
另外這種,跳activity寞射,手機(jī)配置差的渔工,感覺(jué)進(jìn)入動(dòng)畫(huà)擦一下就結(jié)束了。因?yàn)閯?dòng)畫(huà)本身才半秒鐘桥温,activity加載再浪費(fèi)點(diǎn)時(shí)間引矩,你能看到動(dòng)畫(huà)的時(shí)候都快結(jié)束了。。

import android.graphics.Color
import android.os.Bundle
import android.view.View
import com.bumptech.glide.Glide
import com.charliesong.demo0327.R
import com.charliesong.demo0327.base.BaseActivity
import kotlinx.android.synthetic.main.fragment_book_content.*

class ActivityBookContent : BaseActivity() {
    lateinit var coverLocation: CoverLocation
    lateinit var withCoverContainerLayout: WithCoverContainerLayout
    var url = "http://user-gold-cdn.xitu.io/2017/12/13/1604f3080efffc24?imageslim"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.fragment_book_content)
        window.decorView.setBackgroundColor(Color.TRANSPARENT)
        coverLocation=intent.getSerializableExtra("info") as CoverLocation
        withCoverContainerLayout = WithCoverContainerLayout(this).bindContent(tv_content, coverLocation)
        withCoverContainerLayout.loadCoverView().apply {
           Glide.with(this).load(url) .into(this)
        }
    }

    override fun onBackPressed() {
        withCoverContainerLayout.apply {
            backEndListener = {
                //隱藏控件旺韭,否則finish的時(shí)候會(huì)閃一下氛谜,體驗(yàn)不好
                this.visibility= View.INVISIBLE
                finish()
            }
        }
                .backAnimation()
    }
}
  1. 自定義view
    封裝了下容器,處理展開(kāi)關(guān)閉動(dòng)畫(huà)
    核心思路区端,imageView的scaleType設(shè)置為Matrix值漫。
    完事利用imageView的setImageMatrix來(lái)修改圖片的信息
    主要里用setSkew修改了y軸的傾斜度。負(fù)值往上傾斜珊燎,正的往下惭嚣, 對(duì)于x軸來(lái)說(shuō),正的往右邊傾斜悔政,負(fù)的往左晚吞。
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Matrix
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import android.widget.ImageView
import com.charliesong.demo0327.R

class WithCoverContainerLayout:FrameLayout{

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    private val params=ViewGroup.LayoutParams(-1,-1)

    private lateinit var coverLocation: CoverLocation//the clicked cover view's left,top ,width,heigth
    private lateinit var coverView:ImageView//a imageView to display the cover
    private var rootWidth=0//this layout width
    private var rootHeight=0//this layout height
    private var picOriginalWidth=0
    private var picOriginalHegith=0//the load picture's original height
    var animatorTime=500L//animation duration
    private val mx=Matrix()//use for the cover image picture skew

    /**@param view 書(shū)籍打開(kāi)后的內(nèi)容view*/
    fun bindContent(view:View,location:CoverLocation):WithCoverContainerLayout{
        if(view.parent==null||location==null){
            throw  Exception("${view} does not have a parent or coverLocation info is null")
        }
        coverLocation=location
      //默認(rèn)讓這個(gè)控件不可見(jiàn),因?yàn)樗莔atch_parent谋国,我們起初的動(dòng)畫(huà)是從小往大變槽地,不隱藏的話,會(huì)看到閃一下全屏的布局芦瘾,體驗(yàn)不好
        visibility=View.INVISIBLE
        coverView=ImageView(context).apply {
            scaleType=ImageView.ScaleType.MATRIX
            pivotX=0f
        }
        val parent=view.parent as ViewGroup
        parent.clipChildren=false
        parent.removeView(view)
        //把傳進(jìn)來(lái)顯示書(shū)籍內(nèi)容的view從原來(lái)的布局里移除捌蚊,把這個(gè)自定義的添加進(jìn)去,并把這個(gè)書(shū)籍內(nèi)容view添加進(jìn)來(lái)
        parent.addView(this,params)
        this.clipChildren=false;
        this.addView(view)
        this.addView(coverView,params)
        viewTreeObserver.addOnGlobalLayoutListener(object :ViewTreeObserver.OnGlobalLayoutListener{
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                rootWidth=width
                rootHeight=height
                checkPicBound()
                startAnimationIn()
            }
        })
        return  this
    }

    fun loadCoverView():ImageView{
        return coverView
    }
    private fun startAnimationIn(){

        startAnima(ValueAnimator.ofFloat(1f,0f).apply {
            addListener(object :AnimatorListenerAdapter(){
                override fun onAnimationEnd(animation: Animator?) {
                    super.onAnimationEnd(animation)
                    if(detached){
                        return
                    }
                    coverView.visibility=View.INVISIBLE
                }
            })
        })
    }
    fun backAnimation(){
        startAnima(ValueAnimator.ofFloat(cancelValue,1f).apply {
            addListener(object :AnimatorListenerAdapter(){
                override fun onAnimationEnd(animation: Animator?) {
                    super.onAnimationEnd(animation)
                    backEndListener?.invoke()
                }

                override fun onAnimationStart(animation: Animator?) {
                    super.onAnimationStart(animation)
                    coverView.visibility=View.VISIBLE
                }
            })
        })
    }
    //handle back animation end近弟,動(dòng)畫(huà)結(jié)束我們需要關(guān)閉頁(yè)面缅糟,所以需要一個(gè)回調(diào)來(lái)處理
    var backEndListener:(()->Unit)?=null

    //我們得拿到圖片的原始大小,好進(jìn)行縮放處理祷愉,使其完整顯示在imageView里
    private fun checkPicBound(){
        coverView.drawable?.bounds?.apply {
            picOriginalWidth=this.width()
            picOriginalHegith=this.height()
        }
    }
    private var cancelValue=0f//記錄下動(dòng)畫(huà)進(jìn)行到哪里被取消了窗宦。方便反向動(dòng)畫(huà)從這里開(kāi)始
    private var preAnimator:ValueAnimator?=null
    private fun startAnima(animator: ValueAnimator){
        preAnimator?.cancel()
        preAnimator=animator
        animator.apply {
            duration=animatorTime

            addUpdateListener {
                if(detached){
                    return@addUpdateListener
                }
                if(picOriginalWidth==0){
                    checkPicBound()
//有可能圖片加載失敗了,這里就給個(gè)默認(rèn)值二鳄,否則動(dòng)畫(huà)就無(wú)法繼續(xù)下去了
                    if(picOriginalWidth==0){
                        picOriginalWidth=width;
                        picOriginalHegith=height;
                    }
                    return@addUpdateListener
                }
                val value=it.animatedValue as Float
                cancelValue=value
                if(value==1f){
                    return@addUpdateListener
                }
                this@WithCoverContainerLayout.apply {
                    layoutParams=(layoutParams as MarginLayoutParams).apply {
                        leftMargin= (coverLocation.l*value).toInt()
                        topMargin= (coverLocation.t*value).toInt()
                        rightMargin= ((rootWidth-coverLocation.r)*value).toInt()
                        bottomMargin= ((rootHeight-coverLocation.b)*value).toInt()
                    }
                }


                coverView.imageMatrix=mx.apply {
                    this.reset()
                    val xscale=width/picOriginalWidth.toFloat()
                    val yscale=height/picOriginalHegith.toFloat()
                    this.setScale(xscale*(value),yscale)
                     postSkew(0f,-1*(1-value))
                }
                if(this@WithCoverContainerLayout.width<rootWidth){
                    this@WithCoverContainerLayout.visibility=View.VISIBLE
                }
            }

            start()
        }
    }


    private var detached=false
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        detached=false
    }
    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        detached=true
    }
}

簡(jiǎn)單分析下動(dòng)畫(huà)的邏輯

如下圖赴涵,我們知道要?jiǎng)赢?huà)的封面的上下左右距離的。
然后開(kāi)啟一個(gè)valueObject订讼,從0到1或者從1到0變化髓窜,完事這些margin乘以這些比率,就相應(yīng)的發(fā)生變化了欺殿,然后控件的大小也就跟著變化了寄纵。因?yàn)楦淖兊氖沁@個(gè)自定義容器的大小,所以里邊的控件也都跟著變化了祈餐,省事擂啥。
其他的也就對(duì)里邊的imageView的圖片進(jìn)行了下Y軸的傾斜。x軸的縮放而已帆阳。

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蜒谤,更是在濱河造成了極大的恐慌山宾,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳍徽,死亡現(xiàn)場(chǎng)離奇詭異资锰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)阶祭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)绷杜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人濒募,你說(shuō)我怎么就攤上這事鞭盟。” “怎么了瑰剃?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵齿诉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我晌姚,道長(zhǎng)粤剧,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任挥唠,我火速辦了婚禮抵恋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宝磨。我一直安慰自己弧关,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布懊烤。 她就那樣靜靜地躺著梯醒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腌紧。 梳的紋絲不亂的頭發(fā)上茸习,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音壁肋,去河邊找鬼号胚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛浸遗,可吹牛的內(nèi)容都是我干的猫胁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼跛锌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼弃秆!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤菠赚,失蹤者是張志新(化名)和其女友劉穎脑豹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體衡查,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瘩欺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拌牲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俱饿。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖塌忽,靈堂內(nèi)的尸體忽然破棺而出拍埠,到底是詐尸還是另有隱情,我是刑警寧澤砚婆,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布械拍,位于F島的核電站,受9級(jí)特大地震影響装盯,放射性物質(zhì)發(fā)生泄漏坷虑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一埂奈、第九天 我趴在偏房一處隱蔽的房頂上張望迄损。 院中可真熱鬧,春花似錦账磺、人聲如沸芹敌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)氏捞。三九已至,卻和暖如春冒版,著一層夾襖步出監(jiān)牢的瞬間液茎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工辞嗡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捆等,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓续室,卻偏偏與公主長(zhǎng)得像栋烤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挺狰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354