private val itemSpaceDistance = 24f.dp.toInt()
private val horizontalSpace = 18f.dp.toInt()
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
outRect.apply {
this.left = horizontalSpace
this.right = horizontalSpace
this.bottom = itemSpaceDistance
}
if (parent.getChildAdapterPosition(view) == 0) {
outRect.top = itemSpaceDistance
}
}
}
* 给每一个卡片创建点击事件,跳转到DetailFragment,并将卡片对应的数据加载进去:
这里,我使用ViewModel来实现Fragment之间的数据传输:将ViewModel的Provider设置为Activity,这样我们的ViewModel生命周期就跟随着Activity变化,以此帮助我们实现数据传输。
1. 初始化ViewModel,让其生命周期跟着activity走
//我们在HomeFragment.kt
articleCardViewModel = ViewModelProvider(activity).get(ArticleDetailViewModel::class.java)
2. 在这个activity内的任意fragment内,用同样的方式,获取这个viewModel
//我们在DetailFragment.kt
viewModel = ViewModelProvider(activity!!).get(ArticleDetailViewModel::class.java)
3. 卡片点击事件:当前卡片向viewModel传入这个卡片的值,随后由DetailFragment接收,它就能在渲染自身页面的时候获取这些值了,并成为了那个卡片的详情页。
//给recyclerView的每个Item添加点击事件
override fun onItemClick(viewHolder: RecyclerView.ViewHolder?) {
var position = cardRecyclerView.getChildLayoutPosition(viewHolder!!.itemView)
GlobalScope.launch(Dispatchers.Default) {
//更新主副标题、摘要等
articleDetailViewModel.articleCardData = cardArray[position]
//更新背景图片
articleDetailViewModel.updateBackGroundImage(resources, activity!!)
//传入当前item的位置,position
articleDetailViewModel.position = position.toString()
}
//使用Navigation跳转至下一个页面。
}
DetailFragment的布局在\_**article\_detail\_layout.xml**\_的基础上,外部添加了一层ScrollView来展示比较长的正文,并在内部添加了contentText的TextView,整体结构与预览如下所示:
) 
DetailFragment接收数据,并渲染自己的画面:
//in DetailFragment.kt
//viewModel中传入的卡片相关数据
viewModel.articleCardData.apply {
view.findViewById(R.id.mainTitle).text = this.mainTitle
view.findViewById(R.id.cardTitle).text = this.cardTitle
view.findViewById(R.id.rootText).text = this.rootText
view.findViewById(R.id.mainTitle).setTextColor(this.mainTitleColor)
//设置正文
if (this.contentText != “”) {
view.findViewById(R.id.contentText).text = this.contentText
}
//设置背景图
view.findViewById(R.id.cardLinearLayout).background = viewModel.backGroundImage
}
view.findViewById(R.id.backGroundCard).transitionName = “backGroundCard${viewModel.position}”
* 至此,我们完成了静态页面的布局。最后,再用图片的形式梳理一下流程!

### 2.2 卡片与详情页之间的转场动画
终于到了最有意思的部分,这一环节我们请出最核心的角色:`SharedElementTransition共享元素动画`
#### 共享元素动画的使用介绍
>
> 共享元素动画的官方介绍请跳转:[使用过渡为布局变化添加动画效果 | Android 开发者 | Android Developers (google.cn)]( )
>
>
>
>
> 附一个用得比较多的共享元素动画库:[Material-Motion]( )
>
>
>
这里,我用自己的方式介绍一下:
* 共享元素动画既可以用于Fragment间,也可以用于Activity间,使用起来是相当便捷的,**只需要保证共享元素在两个Fragment的TransitionName一致,并在跳转前将其绑定即可。**
* 在这个切换过程,我们可以指定一个Transition动画来实现我们想要的效果,比如Fade()可以渐入渐出,ChangeTransform()实现尺寸变化。
* **Transition动画的底层是属性动画**,他会获取FragmentA中共享元素的某个值**作为起点**,比如位置x=0,y=0,再获取到FragmentB中共享元素的位置x=100,y=100**作为终点**,接着执行一个属性动画,来让这个共享元素平滑地转移过去。
* 知道了这个原理,我们可以很轻松地自定义Transition,只需要重写几个方法,控制我们需要的起点和终点的值,再定义我们想要的属性动画就好。具体可以见官方文档:[创建自定义过渡动画 | Android 开发者 | Android Developers (google.cn)]( )
#### 在RecyclerView中,让Item作为共享元素进行动画
在上面我们提到,想要执行属性动画的前提,是让两个Fragment的共享元素拥有相同的TransitionName,在RecycerView中,我们这样操作:
1. 在创建这些卡片流的时候,我们给**每个卡片的TransitionName赋值为"shared\_card${position}"**,position使它的位次,以此保证他们的TransitionName是独一无二的。
2. 接着,我们在卡片被点击后,**给DetailFragment传入当前被点击卡片的TransitionName**,并让DetailFragment修改自己的那个卡片组件的TransitionName为"shared\_card${position}"
如此,我们便实现了绑定。
接着,便是让每个Item的点击事件添加一条Navigation跳转!(当然也可以用FragmentManager):
a. 我们需要首先创建一个当前View到对应TransitionName的绑定(命名规则上面提过)
//首先创建一个绑定,形式是 view to TransitionName
val extras = FragmentNavigatorExtras(
viewHolder.itemView.findViewById(R.id.backGroundCardView) to “backGroundCard${position}”,
)
b. 然后,我们使用navigate()实现跳转,函数内部我们填入目标fragment ID与先前绑定的\_**extras**\_
view!!.findNavController().navigate(
R.id.action_to_article, null,
null,
extras
)
完成共享元素动画的最后一步,在\_**DetailFragment**\_(目标Fragment)内设置我们需要的Transition效果。 sharedElementEnterTransition对象接受一个Transition类,Transition则包含了我们需要实现的动画效果。这里我们使用的R.transiton.shared是自定义的Transition集合。
//in DetailFragment.kt
sharedElementEnterTransition = TransitionInflater.from(requireContext()).inflateTransition(R.transition.shared)
sharedElementReturnTransition = TransitionInflater.from(requireContext()).inflateTransition(R.transition.shared)
#### 我们使用的共享元素动画Transition:R.transition.shared
<transitionSet android:transitionOrdering="together">
<transition class="isense.com.ui.myTransition.MyCornerTransition">
</transition>
</transitionSet>
<changeBounds android:interpolator="@anim/my_overshoot">
</changeBounds>
<changeTransform android:interpolator="@anim/my_overshoot">
</changeTransform>
在如上代码中,我们定义的Transition包括了三个内容,分别是:changeBounds, CornerTransiton(自己定义的)和changeTransform。我们借助他们来实现所需要的卡片展开效果。
#### 为什么使用OverShootInterpolator?
前面提到,AppStore原生的动画函数曲线是类弹簧的,这与OverShootInterpolator的函数曲线是类似的:
他们都会在到达目标值后,继续向前进一小步,然后再退回来,就像下方的函数曲线一样:  f(t)=t∗t∗((1.2+1)∗t+1.2)+1.0f(t) = t \* t \* ((1.2 + 1) \* t + 1.2) + 1.0f(t)=t∗t∗((1.2+1)∗t+1.2)+1.0
#### 怎么实现其他卡片的模糊?
这里,我借助了Github的开源库:[wasabeef/Blurry: Blurry is an easy blur library for Android (github.com)]( )
它可以实现将当前context的画面转为模糊,并重新映射回rootViewGroup。
viewHolder.itemView.visibility=View.INVISIBLE
Blurry.with(context).radius(25).sampling(1).animate(100).onto(NoiseConstraintLayout)
viewHolder.itemView.visibility=View.VISIBLE
#### 最后,为保证共享动画返回时的效果,请注意:
为了保证DetailFragment返回HomeFragment也能拥有共享动画的效果,请务必在HomeFragment的onCreate()内添加如下代码:
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
如果你觉得还挺有趣,可以点击下方链接浏览更多同类文章:
[“用Android复刻Apple产品UI”(3)—优雅的数据统计图表 - 掘金 (juejin.cn)]( )
[“用Android复刻Apple产品UI”(1)—丝滑的噪声监测音量条 - 掘金 (juejin.cn)]( )
## 写在最后
在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。
**如果你觉得自己学习效率低,缺乏正确的指导,可以评论区留言或私信,加入我们资源丰富,学习氛围浓厚的技术圈一起学习交流吧!**
>
> 这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
>
>
>
**【详细可整理扫描下方二维码免费领取】**

### 尾声
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
**进阶学习视频**

**附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题** (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
、常见算法题汇总。)
[外链图片转存中...(img-dMLqnaZx-1714525722930)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**