android书籍打开动画
2019-03-22 本文已影响12人
有点健忘
嗯嗯,勉强可以接受,效果图如下
20190322_141518.gif
其实了主要就是对imageView进行y轴的旋转,最早就是这个思路,可那个y轴旋转到-45度左右图片就不见了。
然后只能寻找其他办法了。
想起以前对图片进行skew操作,感觉效果差不多就试了下,就是上图的效果,勉强可以。
上代码即可
- CoverLocation实体类
存储下封面的原始位置信息,方便传递到下个页面使用
import java.io.Serializable
data class CoverLocation(var l:Int,var t:Int,var w:Int,var h:Int):Serializable{
//记录点击的封面imageView的left,top,right,bottom,width,height
var r=l+w
var b=t+h
}
- 列表页
数据的加载啥的就自己处理了,这里处理下点击事件
iv_cover:就是 那个封面imageView。我们获取到他的location,宽高,传递给下个页面。
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))
} else {
FragmentBookContent.goFragmentContentPage(activity!!, R.id.container, location)
}
}
}
- 跳转分2种测试
3.1 跳转到fragment
建议用这种
布局fragment_book_content.xml
这里测试就简单弄个textView替代了,实际中你可以是自定义布局,复杂布局,随你啦
<?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>
内容页fragment
代码也不多,封装了一个WithCoverContainerLayout来处理动画,fragment主要就是绑定下我们的内容View
完事处理下后退的时候动画结束以后,弹出这个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)//这个url应该是上个页面传递过来的。
// 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 跳转到activity
这种不太喜欢,咋说了。大家知道页面消失有个动画,会闪一下消失,还得处理,麻烦,还得让activity是透明背景
另外这种,跳activity,手机配置差的,感觉进入动画擦一下就结束了。因为动画本身才半秒钟,activity加载再浪费点时间,你能看到动画的时候都快结束了。。
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的时候会闪一下,体验不好
this.visibility= View.INVISIBLE
finish()
}
}
.backAnimation()
}
}
- 自定义view
封装了下容器,处理展开关闭动画
核心思路,imageView的scaleType设置为Matrix。
完事利用imageView的setImageMatrix来修改图片的信息
主要里用setSkew修改了y轴的倾斜度。负值往上倾斜,正的往下, 对于x轴来说,正的往右边倾斜,负的往左。
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 书籍打开后的内容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
//默认让这个控件不可见,因为它是match_parent,我们起初的动画是从小往大变,不隐藏的话,会看到闪一下全屏的布局,体验不好
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)
//把传进来显示书籍内容的view从原来的布局里移除,把这个自定义的添加进去,并把这个书籍内容view添加进来
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,动画结束我们需要关闭页面,所以需要一个回调来处理
var backEndListener:(()->Unit)?=null
//我们得拿到图片的原始大小,好进行缩放处理,使其完整显示在imageView里
private fun checkPicBound(){
coverView.drawable?.bounds?.apply {
picOriginalWidth=this.width()
picOriginalHegith=this.height()
}
}
private var cancelValue=0f//记录下动画进行到哪里被取消了。方便反向动画从这里开始
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()
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
}
}
简单分析下动画的逻辑
如下图,我们知道要动画的封面的上下左右距离的。
然后开启一个valueObject,从0到1或者从1到0变化,完事这些margin乘以这些比率,就相应的增加介绍了,然后控件的大小也就跟着变化了。因为改变的是这个自定义容器的大小,所以里边的控件也都跟着变化了,省事。
其他的也就对里边的imageView的图片进行了下Y轴的倾斜。x轴的缩放而已。