嗯嗯,勉強(qiáng)可以接受已艰,效果圖如下
其實(shí)了主要就是對(duì)imageView進(jìn)行y軸的旋轉(zhuǎn)宫患,最早就是這個(gè)思路,可那個(gè)y軸旋轉(zhuǎn)到-45度左右圖片就不見(jiàn)了绿饵。
然后只能尋找其他辦法了。
想起以前對(duì)圖片進(jìn)行skew操作瓶颠,感覺(jué)效果差不多就試了下拟赊,就是上圖的效果,勉強(qiáng)可以粹淋。
上代碼即可
- 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
}
- 列表頁(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)
}
}
}
- 跳轉(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()
}
}
- 自定義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軸的縮放而已帆阳。