Android - 仿网易云音乐歌单详情页
2017.01.13
JinBeen
Android
 热度
℃
前段时间模仿网易云音乐UI使用DataBinding做了一个App:CloudReader,今天把其中的类似歌单详情页单独拿出来说一下,我觉得其中还是有些干货的,关联到的知识点还比较有价值,而且也有很多需要注意的地方。
本次项目地址:ScrollShapeUI
效果图对比:
网易云音乐App原图:
模仿的效果图:
建议大家直接看CloudReader项目应用里的效果,里面的内容部分有加载中的loading图,效果更逼真。
基本布局:
FrameLayout
—– MyNestedScrollView // 为了Api23下的滑动兼容
—- LinearLayout // 内容部分
—– RelativeLayout
—- ImageView // Toolbar后面的背景图
—- Toolbar // 标题栏
由于篇幅原因,不能做详细的介绍,这里就简单介绍实现这种效果的思路:
实现思路:
- 1、Activity设置自定义Shared Element切换动画
- 2、透明状态栏(透明Toolbar,使背景图上移)
- 3、Toolbar底部增加和背景一样的高斯模糊图,并上移图片(为了使背景图的底部作为Toolbar的背景)
- 4、上下滑动,通过NestedScrollView拿到移动的高度,同时调整Toolbar的背景图透明度
1、Activity设置自定义元素共享切换动画
大家可以发现页面跳转时图片移动的是一个曲线路径,我们可以定制View的过渡切换效果,这是Material Design中比较常见的用法,Api21以上才有效。需要在开启页面时使用ActivityOptions.makeSceneTransitionAnimation()
,其中定义共享的view和transitionName。然后在对应的Activity里创建ArcMotion
对象。ArcMotion是PathMotion
子类,是个曲线路径,对应代码片:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { ArcMotion arcMotion = new ArcMotion(); arcMotion.setMinimumHorizontalAngle(50f); arcMotion.setMinimumVerticalAngle(50f); Interpolator interpolator = AnimationUtils.loadInterpolator(this, android.R.interpolator.fast_out_slow_in); CustomChangeBounds changeBounds = new CustomChangeBounds(); changeBounds.setPathMotion(arcMotion); changeBounds.setInterpolator(interpolator); changeBounds.addTarget(binding.include.ivOnePhoto); getWindow().setSharedElementEnterTransition(changeBounds); getWindow().setSharedElementReturnTransition(changeBounds); } Intent intent = new Intent(context, MovieDetailActivity.class); intent.putExtra("bean", positionData); ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(context,imageView, CommonUtils.getString(R.string.transition_book_img)); ActivityCompat.startActivity(context, intent, options.toBundle());
|
值得注意的是:因为加载图片要一点时间,切换页面时就会出现闪烁的情况,而如果取的是缓存就不会有这样的问题,所以这里有个小技巧,就是起初Glide加载的图片就指定固定的大小(.override(120,120)
),这样图片就会被缓存起来,等到跳转时就取缓存。具体还请大家看项目源码。
2、透明状态栏
1 2
| StatusBarUtil.setTranslucentImageHeader(this, 0, binding.titleToolBar);
|
其中内容根布局不要设置android:fitsSystemWindows="true"
,这样会额外添加一个状态栏。其中StatusBarUtil,是一个为Android App 设置状态栏的工具类。这里向大家推荐郭霖大神的一篇文章:Android状态栏微技巧,带你真正理解沉浸式模式,里面讲解了透明状态栏和沉浸式状态栏的渊源和有关设置用法。
仔细分析后发现网易云音乐的Toolbar的背景其实显示的是高斯模糊图的底部,所以这里基本套路是Toolbar是透明的,后面背景图取的是高斯模糊图的底部一部分。
1 2 3 4 5 6 7 8 9 10 11 12
| int toolbarHeight = binding.titleToolBar.getLayoutParams().height; final int headerBgHeight = toolbarHeight + StatusBarUtil.getStatusBarHeight(this); binding.ivTitleHeadBg.setVisibility(View.VISIBLE); ViewGroup.LayoutParams params = binding.ivTitleHeadBg.getLayoutParams(); ViewGroup.MarginLayoutParams ivTitleHeadBgParams = (ViewGroup.MarginLayoutParams) binding.ivTitleHeadBg.getLayoutParams(); int marginTop = params.height - headerBgHeight; ivTitleHeadBgParams.setMargins(0, -marginTop, 0, 0); binding.ivTitleHeadBg.setImageAlpha(0);
|
监听图片显示,在显示之后将其设置为透明色,然后在滑动的时候渐变。这里值得注意的是在设置图片时不要设置加载中的图片,不然初始化时达不到透明的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| Glide.with(this).load(NeteasePlaylistActivity.IMAGE_URL_MEDIUM) .error(R.drawable.stackblur_default) .bitmapTransform(new BlurTransformation(this, 14, 3)) .listener(new RequestListener<String, GlideDrawable>() { @Override public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) { return false; } @Override public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) { binding.titleToolBar.setBackgroundColor(Color.TRANSPARENT); binding.ivTitleHeadBg.setImageAlpha(0); binding.ivTitleHeadBg.setVisibility(View.VISIBLE); return false; } }).into(binding.ivTitleHeadBg);
|
其中引入的库应为如下,将官方Glide的额外扩展了,使其可以支持高斯模糊。
1
| compile 'jp.wasabeef:glide-transformations:2.0.1'
|
4、上下滑动,渐变背景图透明度
由于NestedScrollView
滚动监听只能在API23以上才能使用,这里为了兼容需要额外处理,定义滚动接口,具体:MyNestedScrollView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| * 根据页面滑动距离改变Header透明度方法 */ private void scrollChangeHeader(int scrolledY) { if (scrolledY < 0) { scrolledY = 0; } float alpha = Math.abs(scrolledY) * 1.0f / (slidingDistance); Drawable drawable = binding.ivTitleHeadBg.getDrawable(); if (drawable != null) { if (scrolledY <= slidingDistance) { drawable.mutate().setAlpha((int) (alpha * 255)); binding.ivTitleHeadBg.setImageDrawable(drawable); } else { drawable.mutate().setAlpha(255); binding.ivTitleHeadBg.setImageDrawable(drawable); } } }
|
这样基本的效果就实现啦,其中如有需要还可以做些额外的处理,如当背景图不透明时切换标题等~
参考资料
总结
本人思考并实践了很多实现这个页面的方法,目前为止这个方案是最好的,效果体验几乎是一样,其中涉及到的知识点有:1、页面跳转共享元素曲线动画;2、透明状态栏;3、Glide监听图片加载状态和加载固定大小图片等;4、NestedScrollView在Api23下的滑动兼容。如果有更好的方案还请联系我,本次项目的源代码:https://github.com/youlookwhat/ScrollShapeUI。
欢迎关注我的简书和Gayhub