稍微大一点的互联网公司都有属于自己的企业或app的卡通吉祥物。这些品牌元素都是我们
app设计师发挥创意设计的好素材。 也是需要我们巧妙植入到我们APPui界面设计当中去的
最重要的元素。
比如美团、饿了吗、天猫。。。
搅拌咖啡Header
射箭效果Header
仿美团下拉刷新
仿今日头条下拉刷新
YanXuanRefresh
SmartRefreshLottie
一个Android下拉刷新样式
官网方法的实践
838514984
本文内容都为基础和原理,真正实现起来还要写很多代码,这是本文的不足。 不过后来发现有第三方的自定义Header文档写的很好, 建议大家阅读本文之后再阅读一下第三方文档。
SmartRefreshLayout被设计为一个刷新框架,具有非常高的自定性和可扩展性,可以应付 项目中的各种情况和场景。
通过SmartRefreshLayout框架,你可以在一个稳定强大的下拉布局中实现自己项目需求的 Header ,不用去关心滑动事件处理,不用关心子控件的回弹和滚动边界,只需关注自己真 正的项目需求Header的样子和动画。
在学习使用框架的自定义功能之前,我们还是有必要来了解一下框架的体系和结构:
- RefreshLayout 下拉的基本功能,包括布局测量、滑动事件处理、参数设定等等
- RefreshHeader 下拉头部的事件处理和显示接口
- RefreshFooter 上拉底部的事件处理和显示接口
- RefreshContent 对不同内容的统一封装,包括判断是否可滚动、回弹判断、智能识别
下面是UML关系类图
这里还有一份更完整的类图,所有的类都在里面。
网上其他的开源下拉控件一样的可以自定义 Header 和 Footer ,SmartRefreshLayout 和它们 比起来有什么优势?
- Translate 平行移动 特点: 最常见,HeaderView高度不会改变,
- Scale 拉伸形变 特点:在下拉和上弹(HeaderView高度改变)时候,会自动触发OnDraw事件
- FixedFront 固定在前面 特点:不会上下移动,HeaderView高度不会改变
- FixedBehind 固定在后面 特点:不会上下移动,HeaderView高度不会改变(类似微信浏览器效果)
- Screen 全屏幕 特点:固定在前面,尺寸充满整个布局
SmartRefreshLayout 的Header和Footer都有多种变换方式,适应不同风格的 Header 和 Footer,下面是不同变换方式Header的Demo
FixedBehind 固定在后面 和 Scale 拉伸形变
Screen 全屏幕 和 Translate 平行移动
Header和Footer 可以独立的处理手指滑动事件来为动画提供操作指令,也可以使用RefreshLayout的核心接口来完成一些不寻常的操作指令。 下面的打砖块 Header中 ,Header可以独立的使用滑动事件来为游戏挡板提供指令,并同时可以调用核心接口来通知RefreshLayout上下滚动列表
实现自定义Header的第一步就是实现RefreshHeader接口,我们来看看RefreshHeader接口的定义和详细说明
public interface RefreshHeader {
/**
* 获取真实视图(必须返回,不能为null)
*/
@NonNull
View getView();
/**
* 获取变换方式(必须指定一个:平移、拉伸、固定、全屏)
*/
SpinnerStyle getSpinnerStyle();
/**
* 设置主题颜色 (如果自定义的Header没有注意颜色,本方法可以什么都不处理)
* @param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor
*/
void setPrimaryColors(@ColorInt int ... colors);
/**
* 尺寸定义初始化完成 (如果高度不改变(代码修改:setHeader),只调用一次, 在RefreshLayout#onMeasure中调用)
* @param kernel RefreshKernel 核心接口(用于完成高级Header功能)
* @param height HeaderHeight or FooterHeight
* @param maxDragHeight 最大拖动高度
*/
void onInitialized(RefreshKernel kernel, int height, int maxDragHeight);
/**
* 开始动画(开始刷新或者开始加载动画)
* @param layout RefreshLayout
* @param height HeaderHeight or FooterHeight
* @param maxDragHeight 最大拖动高度
*/
void onStartAnimator(RefreshLayout layout, int height, int maxDragHeight);
/**
* 动画结束
* @param layout RefreshLayout
* @param success 数据是否成功刷新或加载
* @return 完成动画所需时间 如果返回 Integer.MAX_VALUE 将取消本次完成事件,继续保持原有状态
*/
int onFinish(RefreshLayout layout, boolean success);
/**
* 手指拖动下拉(会连续多次调用,用于实时控制动画关键帧)
* @param percent 下拉的百分比 值 = offset/headerHeight (0 - percent - (headerHeight+maxDragHeight) / headerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (headerHeight+maxDragHeight)
* @param headerHeight Header的高度
* @param maxDragHeight 最大拖动高度
*/
void onPulling(float percent, int offset, int headerHeight, int maxDragHeight);
/**
* 手指释放之后的持续动画(会连续多次调用,用于实时控制动画关键帧)
* @param percent 下拉的百分比 值 = offset/headerHeight (0 - percent - (headerHeight+maxDragHeight) / headerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (headerHeight+maxDragHeight)
* @param headerHeight Header的高度
* @param maxDragHeight 最大拖动高度
*/
void onReleasing(float percent, int offset, int headerHeight, int maxDragHeight);
}
接下来我们通过实现一个最简单的经典Header(没有更新时间),来慢慢了解自定义Header的流程。
我们的经典Header需要一个标题文本、刷新动画、下拉箭头。由此我们可以选定一个继承LinearLayout并列出成员变量
public class ClassicsHeader extends LinearLayout implements RefreshHeader {
private TextView mHeaderText;//标题文本
private PathsView mArrowView;//下拉箭头
private ImageView mProgressView;//刷新动画视图
private ProgressDrawable mProgressDrawable;//刷新动画
public ClassicsHeader(Context context) {
super(context);
setGravity(Gravity.CENTER_HORIZONTAL);
mHeaderText = new TextView(context);
mProgressDrawable = new ProgressDrawable();
mArrowView = new PathsView(context);
mProgressView = new ImageView(context);
mProgressView.setImageDrawable(mProgressDrawable);
addView(mProgressView);
addView(mArrowView, lpProgress);
addView(mHeaderText, lpHeaderText);
}
}
根据我们的常识,经典Header在下拉的时候是贴着列表平移向下冒出,所以我们实现样式直接指定为:平移
public class ClassicsHeader extends LinearLayout implements RefreshHeader {
@NonNull
public View getView() {
return this;//真实的视图就是自己,不能返回null
}
@Override
public SpinnerStyle getSpinnerStyle() {
return SpinnerStyle.Translate;//指定为平移,不能null
}
}
接下来我们需要在关键地方对动画进行控制和开启
public class ClassicsHeader extends LinearLayout implements RefreshHeader {
@Override
public void onStartAnimator(RefreshLayout layout, int headHeight, int maxDragHeight) {
mProgressDrawable.start();//开始动画
}
@Override
public int onFinish(RefreshLayout layout, boolean success) {
mProgressDrawable.stop();//停止动画
if (success){
mHeaderText.setText("刷新完成");
} else {
mHeaderText.setText("刷新失败");
}
return 500;//延迟500毫秒之后再弹回
}
}
我们还要在不同的状态控制内部控件的显示和旋转
public class ClassicsHeader extends LinearLayout implements RefreshHeader {
@Override
public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) {
switch (newState) {
case None:
case PullDownToRefresh:
mHeaderText.setText("下拉开始刷新");
mArrowView.setVisibility(VISIBLE);//显示下拉箭头
mProgressView.setVisibility(GONE);//隐藏动画
mArrowView.animate().rotation(0);//还原箭头方向
break;
case Refreshing:
mHeaderText.setText("正在刷新");
mProgressView.setVisibility(VISIBLE);//显示加载动画
mArrowView.setVisibility(GONE);//隐藏箭头
break;
case ReleaseToRefresh:
mHeaderText.setText("释放立即刷新");
mArrowView.animate().rotation(180);//显示箭头改为朝上
break;
}
}
}
虽然接口的其他方法我们不用特意去实现,但是方法体还是要声明一下,整合之后完整的代码如下:
public class ClassicsHeader extends LinearLayout implements RefreshHeader {
private TextView mHeaderText;//标题文本
private PathsView mArrowView;//下拉箭头
private ImageView mProgressView;//刷新动画视图
private ProgressDrawable mProgressDrawable;//刷新动画
public ClassicsHeader(Context context) {
super(context);
initView(context);
}
public ClassicsHeader(Context context, AttributeSet attrs) {
super(context, attrs);
this.initView(context);
}
public ClassicsHeader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.initView(context);
}
private void initView(Context context) {
setGravity(Gravity.CENTER);
mHeaderText = new TextView(context);
mProgressDrawable = new ProgressDrawable();
mArrowView = new PathsView(context);
mProgressView = new ImageView(context);
mProgressView.setImageDrawable(mProgressDrawable);
mArrowView.parserPaths("M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z");
addView(mProgressView, SmartUtil.dp2px(20), SmartUtil.dp2px(20));
addView(mArrowView, SmartUtil.dp2px(20), SmartUtil.dp2px(20));
addView(new View(context), SmartUtil.dp2px(20), SmartUtil.dp2px(20));
addView(mHeaderText, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
setMinimumHeight(SmartUtil.dp2px(60));
}
@NonNull
public View getView() {
return this;//真实的视图就是自己,不能返回null
}
@Override
public SpinnerStyle getSpinnerStyle() {
return SpinnerStyle.Translate;//指定为平移,不能null
}
@Override
public void onStartAnimator(RefreshLayout layout, int headHeight, int maxDragHeight) {
mProgressDrawable.start();//开始动画
}
@Override
public int onFinish(RefreshLayout layout, boolean success) {
mProgressDrawable.stop();//停止动画
if (success){
mHeaderText.setText("刷新完成");
} else {
mHeaderText.setText("刷新失败");
}
return 500;//延迟500毫秒之后再弹回
}
@Override
public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) {
switch (newState) {
case None:
case PullDownToRefresh:
mHeaderText.setText("下拉开始刷新");
mArrowView.setVisibility(VISIBLE);//显示下拉箭头
mProgressView.setVisibility(GONE);//隐藏动画
mArrowView.animate().rotation(0);//还原箭头方向
break;
case Refreshing:
mHeaderText.setText("正在刷新");
mProgressView.setVisibility(VISIBLE);//显示加载动画
mArrowView.setVisibility(GONE);//隐藏箭头
break;
case ReleaseToRefresh:
mHeaderText.setText("释放立即刷新");
mArrowView.animate().rotation(180);//显示箭头改为朝上
break;
}
}
@Override
public boolean isSupportHorizontalDrag() {
return false;
}
@Override
public void onInitialized(RefreshKernel kernel, int height, int maxDragHeight) {
}
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
}
@Override
public void onPulling(float percent, int offset, int headHeight, int maxDragHeight) {
}
@Override
public void onReleasing(float percent, int offset, int headHeight, int maxDragHeight) {
}
@Override
public void onRefreshReleased(RefreshLayout layout, int headerHeight, int maxDragHeight) {
}
@Override
public void setPrimaryColors(@ColorInt int ... colors){
}
}
具体方法和 RefreshHeader 非常相似,这里就不再演示了