转载请标明出处:
http://blog.youkuaiyun.com/xuehuayous/article/details/50394640
本文出自:【Kevin.zhou的博客】
一、 闲扯
我们在《PullToRefresh 分析之二、UI结构》提到刷新加载的样式默认的两种样式如下:
但是我们的需求或许是这样的:
那么如果是这样的会不会使用户体验更好些,当然这不是我们开发人员能决定的,但是我们还是要掌握快速定制出这些动画的方法的。其实一个复杂的动画都是一系列简单动作的集合。
通过分析,这些动画其实正是和我们刷新加载的几个状态相对应:
二、 样式扩展基类封装
基于以上分析,我们只要把几个关键的时间点回调出来,让大家去自己定义自己的样式就可以了。这里我写了个LoadingLayoutBase基类,然后该类有一些抽象方法,只要继承了该基类实现方法,就可以啦。由于修改PullToRefresh框架的地方还是比较多的,最后会给大家提供源码,大家可以自己看下还是比较简单的。这里只是讲下如何使用。
下面以京东商城为例进行说明如何扩展,当然美团和汽车之家的扩展也会给大家提供源码的。
(一)、扩展京东样式
1、继承LoadingLayoutBase,实现抽象方法
大家可以把"Copy JavaDoc"勾选上,这样就是把说明也添加过来。
2. 实现父类构造函数
我们在最后使用的时候是通过代码new 对象的方式创建实例,这里只实现带有Context的构造函数就可以了。
现在我们得到了一个架子:
/**
* Created by zhouwk on 2015/12/24 0024.
*/
public class JingDongHeaderLayout extends LoadingLayoutBase{
public JingDongHeaderLayout(Context context) {
super(context);
}
/**
* get the LoadingLayout height or width
*
* @return size
*/
@Override
public int getContentSize() {
return 0;
}
/**
* Call when the widget begins to slide
*/
@Override
public void pullToRefresh() {
}
/**
* Call when the LoadingLayout is fully displayed
*/
@Override
public void releaseToRefresh() {
}
/**
* Call when the LoadingLayout is sliding
*
* @param scaleOfLayout scaleOfLayout
*/
@Override
public void onPull(float scaleOfLayout) {
}
/**
* Call when the LoadingLayout is fully displayed and the widget has released.
* Used to prompt the user loading data
*/
@Override
public void refreshing() {
}
/**
* Call when the data has loaded
*/
@Override
public void reset() {
}
/**
* Set Text to show when the Widget is being Pulled
* <code>setPullLabel(releaseLabel, Mode.BOTH)</code>
*
* @param pullLabel - CharSequence to display
*/
@Override
public void setPullLabel(CharSequence pullLabel) {
}
/**
* Set Text to show when the Widget is refreshing
* <code>setRefreshingLabel(releaseLabel, Mode.BOTH)</code>
*
* @param refreshingLabel - CharSequence to display
*/
@Override
public void setRefreshingLabel(CharSequence refreshingLabel) {
}
/**
* Set Text to show when the Widget is being pulled, and will refresh when
* released. This is the same as calling
* <code>setReleaseLabel(releaseLabel, Mode.BOTH)</code>
*
* @param releaseLabel - CharSequence to display
*/
@Override
public void setReleaseLabel(CharSequence releaseLabel) {
}
}
看起来一大坨还是不少的,其实就如下几个方法,然后给大家写成人话:
3. 写"加载头部"布局
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<FrameLayout
android:id="@+id/fl_inner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/header_footer_top_bottom_padding"
android:paddingLeft="@dimen/header_footer_left_right_padding"
android:paddingRight="@dimen/header_footer_left_right_padding"
android:paddingTop="@dimen/header_footer_top_bottom_padding" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"
android:orientation="vertical" >
<TextView
android:id="@+id/pull_to_refresh_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="让购物更便捷"
android:textColor="#5b5b5b"
android:textAppearance="?android:attr/textAppearance" />
<TextView
android:id="@+id/pull_to_refresh_sub_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="下拉刷新"
android:textColor="#5b5b5b"
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/pull_to_refresh_people"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="40dp"
android:src="@mipmap/app_refresh_people_0" />
<ImageView
android:id="@+id/pull_to_refresh_goods"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@id/pull_to_refresh_people"
android:layout_centerVertical="true"
android:src="@mipmap/app_refresh_goods_0" />
</RelativeLayout>
</FrameLayout>
</merge>
布局比较简单,效果是这样的:
4. 初始化布局视图
public JingDongHeaderLayout(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.jingdong_header_loadinglayout, this);
mInnerLayout = (FrameLayout) findViewById(R.id.fl_inner);
mHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_text);
mSubHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_sub_text);
mGoodsImage = (ImageView) mInnerLayout.findViewById(R.id.pull_to_refresh_goods);
mPersonImage = (ImageView) mInnerLayout.findViewById(R.id.pull_to_refresh_people);
LayoutParams lp = (LayoutParams) mInnerLayout.getLayoutParams();
lp.gravity = Gravity.BOTTOM;
// Load in labels
mPullLabel = context.getString(R.string.jingdong_pull_label);
mRefreshingLabel = context.getString(R.string.jingdong_refreshing_label);
mReleaseLabel = context.getString(R.string.jingdong_release_label);
reset();
}
大家要注意的是初始化布局,"刷新头部"的时候要加上这个:
LayoutParams lp = (LayoutParams) mInnerLayout.getLayoutParams();
lp.gravity = Gravity.BOTTOM;
如果是"加载尾部",就是这样的:
LayoutParams lp = (LayoutParams) mInnerLayout.getLayoutParams();
lp.gravity = Gravity.TOP;
// 获取"加载头部"高度
@Override
public int getContentSize() {
return mInnerLayout.getHeight();
}
6. 下拉过程动画编写
我们再把效果图拿过来研究下:
可以发现在下拉的时候的动画比较简单,就是京东小哥和包裹都由小变大。我们就想到要在下拉开始的回调以及下拉过程的回调中去写。
// 开始下拉时的回调
@Override
public void pullToRefresh() {
mSubHeaderText.setText(mPullLabel);
}
// 下拉拖动时的回调
@Override
public void onPull(float scaleOfLayout) {
scaleOfLayout = scaleOfLayout > 1.0f ? 1.0f : scaleOfLayout;
if (mGoodsImage.getVisibility() != View.VISIBLE) {
mGoodsImage.setVisibility(View.VISIBLE);
}
//透明度动画
ObjectAnimator animAlphaP = ObjectAnimator.ofFloat(mPersonImage, "alpha", -1, 1).setDuration(300);
animAlphaP.setCurrentPlayTime((long) (scaleOfLayout * 300));
ObjectAnimator animAlphaG = ObjectAnimator.ofFloat(mGoodsImage, "alpha", -1, 1).setDuration(300);
animAlphaG.setCurrentPlayTime((long) (scaleOfLayout * 300));
//缩放动画
ViewHelper.setPivotX(mPersonImage, 0); // 设置中心点
ViewHelper.setPivotY(mPersonImage, 0);
ObjectAnimator animPX = ObjectAnimator.ofFloat(mPersonImage, "scaleX", 0, 1).setDuration(300);
animPX.setCurrentPlayTime((long) (scaleOfLayout * 300));
ObjectAnimator animPY = ObjectAnimator.ofFloat(mPersonImage, "scaleY", 0, 1).setDuration(300);
animPY.setCurrentPlayTime((long) (scaleOfLayout * 300));
ViewHelper.setPivotX(mGoodsImage, mGoodsImage.getMeasuredWidth());
ObjectAnimator animGX = ObjectAnimator.ofFloat(mGoodsImage, "scaleX", 0, 1).setDuration(300);
animGX.setCurrentPlayTime((long) (scaleOfLayout * 300));
ObjectAnimator animGY = ObjectAnimator.ofFloat(mGoodsImage, "scaleY", 0, 1).setDuration(300);
animGY.setCurrentPlayTime((long) (scaleOfLayout * 300));
}
看着代码很多,其实思路非常简单,就是设置变大的动画随着拖动的距离大小变化,这里用到了nineoldandroids这个动画兼容库。
7. "加载头部"完全显示时更改提示显示
我们发现开始的提示是"下拉可以刷新"后来变为了"松开可以刷新",这个的设置就是在 "加载头部"完全显示的回调中设置的。
// "加载头部"完全显示时的回调
@Override
public void releaseToRefresh() {
mSubHeaderText.setText(mReleaseLabel);
}
8. 正在加载的设置
手指释放后,我们看到一个京东小哥在飞奔,就是在 释放后刷新时的回调 中设置的。
// 释放后刷新时的回调
@Override
public void refreshing() {
mSubHeaderText.setText(mRefreshingLabel);
if (animP == null) {
mPersonImage.setImageResource(R.drawable.refreshing_anim);
animP = (AnimationDrawable) mPersonImage.getDrawable();
}
animP.start();
if (mGoodsImage.getVisibility() == View.VISIBLE) {
mGoodsImage.setVisibility(View.INVISIBLE);
}
}
这里我们使用的是帧动画,就是几张图片刷啊刷,给人的假象就是京东小哥正在卖力跑。
9. 初始化到未刷新状态
// 初始化到未刷新状态
@Override
public void reset() {
if (animP != null) {
animP.stop();
animP = null;
}
mPersonImage.setImageResource(R.mipmap.app_refresh_people_0);
if (mGoodsImage.getVisibility() == View.VISIBLE) {
mGoodsImage.setVisibility(View.INVISIBLE);
}
}
就是把我们加载时的动画关掉。
10. 设置提示
@Override
public void setPullLabel(CharSequence pullLabel) {
mPullLabel = pullLabel;
}
@Override
public void setRefreshingLabel(CharSequence refreshingLabel) {
mRefreshingLabel = refreshingLabel;
}
@Override
public void setReleaseLabel(CharSequence releaseLabel) {
mReleaseLabel = releaseLabel;
}
这三个方法不实现也可以,因为我们是通过以下方式初始提示的
mPullLabel = context.getString(R.string.jingdong_pull_label);
mRefreshingLabel = context.getString(R.string.jingdong_refreshing_label);
mReleaseLabel = context.getString(R.string.jingdong_release_label);
之所以抽象出来这三个方法,是可以让大家更灵活地改变提示语。
三、设置自定义样式
一行代码搞定
mPullToRefreshRecyclerView.setHeaderLayout(new JingDongHeaderLayout(this));
当然也可以设置底部样式:
mPullToRefreshRecyclerView.setFooterLayout(xxx);
四、源码
五、结语
在该篇中,我们通过修改PullToRefresh框架实现了简单扩展刷新加载头部尾部的样式配置。那么关于PullToRefresh的五篇文章就完结啦。