主要节点
效果
实现方案
- ViewPager
- CoordinatorLayout
- Behavior
Behavior设置在哪里?
- 设置在 你要根据这个View的位置变化而做处理的View上,比如A发生变化,能触发B的变化,所以你要设置在A上,然后在自定义的Behavior处理,这样能通过A的变化来改变其他View。(个人比较喜欢这种方式处理)
- 设置在B上,然后在自定义的Behavior通过layoutDependsOn来指定B要根据哪些View来的变化来变化,这样可以指定多个变化来改变B。 这个也是官方的,在layoutDependsOn的方法入参child就是B,而dependency是parent下的其他View,总之,你依赖的那个dependency发生了变化才会触发Behavior的方法回调。
ViewPager样式修改
ViewPager切换Item间隔处理
ViewPageronPageScroll监听
@Override
public void onPageScroll(int enterPosition, int leavePosition, float percent) {
try {
String picUrl = mBanners.get(enterPosition).getBackground();
if (noEverLoad(picUrl)) {
vArcTarget.setTag(R.id.banner_tag, picUrl);
MMCImageLoader.getInstance().loadUrlImage(getActivity(),
picUrl, vArcTarget, R.color.transparent);
}
vArcTarget.setAlpha(percent);
//当超过0.96的时候设置当前的背景图,
// 如果在onPageSelect或者state等于SCROLL_STATE_IDLE设置太晚了
if (percent >= 0.96F) {
MMCImageLoader.getInstance().loadUrlImage(getActivity(),
picUrl, vArcCurrent, R.drawable.holder);
}
} catch (Exception e) {
//下标异常,空指针异常
}
}
渐变效果处理
一张图的渐变方案(自定义View绘制)例如
public class ArcView extends android.support.v7.widget.AppCompatImageView {
/**
* rgb渐变
* argbEvaluator.evaluate(int )
*/
ArgbEvaluator argbEvaluator = new ArgbEvaluator();
Paint mPaint = new Paint();
RectF vRectF = new RectF();
{
mPaint.setColor(ContextCompat.getColor(getContext(), R.color.base_color_primary));
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
public ArcView(Context context) {
this(context, null, 0);
}
public ArcView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ArcView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float v = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, getContext().getResources().getDisplayMetrics());
vRectF.set(-v, -getHeight(), getWidth() + v, getHeight());
canvas.drawArc(vRectF, 0F, 180, true, mPaint);
}
public void setPaintColor(int color) {
mPaint.setColor(color);
invalidate();
}
}
两张图片的渐变方案,适合图片,预缓存效果更好
vArcCurrent在下方,vArcTarget在上方,通过加载上面那张之后,根据滑动进度修改透明度,当超过96%透明度的时候将底部的vArcCurrent加载了,即达到掩饰效果,但是首次加载效果依赖网速,本地有缓存之后就完美了。如果想要完美效果需要将图片先预加载Bitmap。这里预加载可以单独判断或者先缓存。
三张图片的方案,提前设置好前后两张图片
在两张图的基础上多一些代码。。。
CoordinatorLayout,Behavior流程,源码简单分析
通过获取CoordinatorLayout的子View的LayoutParms反射解析并创建Behavior实例
然后将CoordinatorLayout.Behavior的位置变化关联给Behavior,这样CoordinatorLayout的一些变化就调用Behavior相应的方法,然后你去Behavior处理方法就行了~
具体可以查看CoordinatorLayout下NestedScrollingParent2接口方法的相关实现
https://developer.android.com/reference/android/support/v4/view/NestedScrollingParent2
它把dependency加在节点Node,而child加在了Edge上
而这个方法来收集所有的child到mDependencySortedChildren这个List,mChildDag是DirectedAcyclicGraph的实例,这个东西叫做“有向无环图”,即DAG,这里用到的就是树
如果没有解析到behavior就设置一个默认的behavior
通过两个for循环比较 onChildViewsChanged这个方法会回调
选取两张图片方案的实操过程
- 继承 CoordinatorLayout.Behavior编写 Behavior的全路径名,例如com.xxx.xxx.base.lib.view.banner.BannerBehavior或者用".类名"来指定
- 编写Behavior有两种思路
1 在CoordinatorLayout层级下设置在你想要改变的那个View上,然后通过重写Behavior的layoutDependsOn方法找到CoordinatorLayout节点下的某个你需要跟随变化的View A,然后你可以在onDependentViewChanged来根据dependency的变化来改变child;(如果发现无效果,请看方法2)
2 滚动类型的,比如我们的滚动改变透明度,可以两种方式- child与dependency有间距,移动后位于denpency边界处,例如上方,下方,那么我们可以用默认的@string/appbar_scrolling_view_behavior来处理
- 相对于紧贴着控件,比如child上方贴着dependency下方的控件,继续往上滑动之后,并不会回调onDependentViewChanged方法
如果你第一次使用,建议默认创建ScrollingActivity看看代码和效果
布局会有
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ScrollingActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:fitsSystemWindows="true"
android:layout_height="@dimen/app_bar_height"
android:layout_width="match_parent"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:toolbarId="@+id/toolbar"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/colorPrimary">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/large_text"/>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
而自带的behavior有这几个
定义Behavior
public class BannerBehavior extends CoordinatorLayout.Behavior<View> {
private int value;
public BannerBehavior() {}
public BannerBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(
@NonNull CoordinatorLayout coordinatorLayout,
@NonNull View child,
@NonNull View directTargetChild,
@NonNull View target, int axes, int type) {
//竖直滑动的才拦截
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child,
@NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
value += dyConsumed;
//这里用getRootView()是因为topbar_title_layout和top_bar_bg不在当前的layout上。而为了方便找arc_bg我也这样获取了,你也可以设置成dependencyView
View titleBar = coordinatorLayout.getRootView().findViewById(R.id.topbar_title_layout);
View topBarBg = coordinatorLayout.getRootView().findViewById(R.id.top_bar_bg);
View arcBg = coordinatorLayout.getRootView().findViewById(R.id.arc_bg);
int height = titleBar.getHeight();
// titleBar高度的时候全部显示
// float abs =Math.min(height, Math.max(value, 0F)) / height;
// titleBar高度一半的时候全部显示,但是最大透明度值不能超过1
float abs = Math.min(1, Math.min(height, Math.max(value, 0F)) / height * 2);
arcBg.setAlpha(abs);
topBarBg.setAlpha(abs);
}
}
设置behavior
然后我这边的布局是这样的将base_banner_behavior设置在StatusView上,只能设置在CoordinatorLayout节点下
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.linghit.lingjidashi.base.lib.view.StatusView
android:id="@+id/base_state_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/base_banner_behavior">
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/base_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:srlDisableContentWhenRefresh="true">
<android.support.v7.widget.RecyclerView
android:id="@+id/base_refresh_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
</com.linghit.lingjidashi.base.lib.view.StatusView>
</android.support.design.widget.CoordinatorLayout>
onStartNestedScroll onNestedScroll的回调流程
在CoordinatorLayout的onStartNestedScroll方法是这样的
而setNestedScrollAccepted只是设置下这两种状态下是否接收,然后通过isNestedScrollAccepted获取状态,如果accepted就回调onNestedScrollAccepted给Behavior,所以相应的其他方法在一些相应的状态会回调,而你在你想要的方法回调处理就行,比如我的需要根据滑动状态即时改变状态,所以我选择在onNestedScroll处理。
Demo:Gayhub直连
番外篇:喜马拉雅FM首页源码剖析实现过程
PS:
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getContext().getResources().getDisplayMetrics());
- getContext().getResources().getIdentifier(“login_xx_” + code,
“string”, getActivity().getPackageName());
其他收获
CoordinatorLayout分析参考