没有废话,直接撸代码~
首先是自定义控件:
/**
* Created by HXY on 2018/9/25.
* Be used for : 自定义首页Scrollview主布局,添加下拉刷新子Recycleview,滑动事件监听
*/
public class HomeScrollview extends ScrollView {
private int down_y; //按下时候的y坐标
private int scroll_y; //ScrollView的滑动距离
private View headViewRefresh; //头布局
private RefreshListener listsner; //刷新加载数据监听
private boolean b_down; //是否可以刷新
private int viewWidth; //scrollView宽度
private int headViewHeight; //头布局刷新时的高度
public HomeScrollview(Context context) {
super(context,null);
}
public HomeScrollview(Context context, AttributeSet attrs) {
super(context, attrs,0);
}
public HomeScrollview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = getWidth(); //获取ScrollView的宽度用来设置头布局的宽度
}
/**
* 设置刷新头布局
* @param view
*/
public void setHeadView(View view){ //在引用此自定义ScrollView的activity中传入初始化完成的头布局文件
this.headViewRefresh = view;
this.headViewHeight = UIUtil.dip2px(getContext(),50); //dp转px,px转dp工具,下文给出
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) headViewRefresh.getLayoutParams();
params.width = viewWidth;
params.height = 0;
headViewRefresh.setLayoutParams(params); //将headView的高度重新设置为0,也就是不可见,为什么这么设置?下文会介绍
}
/**
* 提供给调用scrollView的页面的刷新加载回调方法
* @param listsner
*/
public void setRefreshListener(RefreshListener listsner){ //回调接口下文给出
this.listsner = listsner;
}
@Override
protected void onScrollChanged(int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
super.onScrollChanged(scrollX, scrollY, oldScrollX, oldScrollY);
scroll_y = scrollY; //监听赋值,监听scrollView的滑动状态,当滑动到顶部的时候才可以下拉刷新
if(scrollY == 0){
}else if(scrollY+this.getMeasuredHeight() == this.getChildAt(0).getMeasuredHeight()){ //滑动距离+scrollView的高度如果等于scrollView的内部子view的高度则证明滑动到了底部,则自动加载更多数据
listsner.loadMore(); //加载更多
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) { //重写dispatchTouchEvent,敲黑板,这是重点!
if(event.getAction() == MotionEvent.ACTION_DOWN){ //获取手指初次触摸位置
down_y = (int) event.getY(); //记录下手指点下的纵坐标
}
if(event.getAction() == MotionEvent.ACTION_MOVE){ //滑动事件
if(scroll_y == 0){ //如果scroll_y == 0,在顶部,可以刷新
if(event.getY() - down_y > 0){ //手势判断:向下滑动,可以刷新
//event.getY()-down_y是手指滑动的纵向距离,为什么乘1/3?为了让下拉刷新更肉一点,这样手指下滑300像素,头布局高度增高100像素,可根据个人喜好做出调整
int downRange = (int) ((event.getY()-down_y)*1/3); //给headView动态设置高度,动态高度是手指向下滑动距离的1/3
// headViewRefresh.setVisibility(View.VISIBLE); //显示刷新的根视图 显示headView控件
b_down = false; //刚开始滑动,松手还不可以刷新
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) headViewRefresh.getLayoutParams(); //将滑动距离转化后的值用来给headView动态设置高度
params.width = viewWidth;
params.height = downRange;
headViewRefresh.setLayoutParams(params);
if(downRange >= headViewHeight){ //当动态设置的高度大于初始高度的时候,变换hint,此时松手可刷新;headViewHeight是我设置的初始高度50dp,也用来判断下拉到什么程度才能刷新
listsner.hintChange("松开刷新");//超过了设定的高度,可以刷新,如果不设置这个或者设置的值太小,轻轻一拉就刷新,体验不好
b_down = true; //可以刷新,如果此时抬起手指就可以刷新了
}else{ //当动态设置的高度不大于初始高度的时候,变换hint,此时松手不可刷新
listsner.hintChange("下拉刷新");
b_down = false; //不可以刷新
}
listsner.setWidthX((int)event.getX()); //设置触摸点的横坐标,用来优化头布局效果,不是非必须的
return true; //拦截触摸事件,scrollView不可响应触摸事件,否则会造成松手滑动跳动错位
}else{ //手势判断:小于0则是上滑,此时按正常程序走
b_down = false; //不可以刷新
return super.dispatchTouchEvent(event); //向上滑动,不拦截
}
}else{ //scroll_y不等于0则是上滑,此时按正常程序走
b_down = false; //不可以刷新
return super.dispatchTouchEvent(event); //scrollView不在顶部,不拦截
}
}
if(event.getAction() == MotionEvent.ACTION_UP){ //抬起手指
if(b_down){ //如果可以刷新
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) headViewRefresh.getLayoutParams(); //设置headView为原始高度
params.width = viewWidth;
params.height = headViewHeight;
headViewRefresh.setLayoutParams(params);
listsner.hintChange("正在刷新");
listsner.startRefresh();
}else{ //如果不可以刷新,停止刷新
stopRefresh();
}
}
return super.dispatchTouchEvent(event);
}
/**
* 刷新停止,给scrollView外部调用
*/
public void stopRefresh() {
listsner.hintChange("下拉刷新"); //停止刷新之后,将提示文字设置成初始值,时刻准备着下次刷新
// headViewRefresh.setVisibility(View.GONE); //隐藏headView
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) headViewRefresh.getLayoutParams(); //将headView的高度重新设置为1
params.width = viewWidth;
params.height = 0;
headViewRefresh.setLayoutParams(params); //设置头布局的高度为0,也就是隐藏头布局
}
}
以上是我们的自定义ScrollView ,其中涉及到两个类:
/**
* Created by HXY on 2018/9/25.
* Be used for : HomeScrollview设置头布局所需工具类
*/
public class UIUtil {
/**
* dip转换px
*/
public static int dip2px(Context context, int dip) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dip * scale + 0.5f);
}
/**
* pxz转换dip
*/
public static int px2dip(Context context, int px) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (px / scale + 0.5f);
}
}
/**
* Created by HXY on 2018/9/25.
* Be used for : HomeScrollview监听接口
*/
public interface RefreshListener {
void startRefresh(); //刷新
void loadMore(); //加载
void hintChange(String hint); //提示文字
void setWidthX(int x); //设置x
}
准备就绪,就可以在XML中引用了(去除自带滚动条,如果需要保留,直接把最后一条属性删掉即可):
外部调用监听:
homeSC = view.findViewById(R.id.homeScrollView);
homeSC.setRefreshListener(new RefreshListener() {
@Override
public void startRefresh() {
//以下为测试OK工具类的代码
HashMap dataMap = new HashMap<>();
dataMap.put("phone","185XXXXXXXX");
OKUtils.getInstance().postJson(API.GET_VERIFICATION_CODE,dataMap, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String string = response.body().string();
Log.d("------MainActivity", string);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
homeSC.stopRefresh();
}
});
}
});
}
@Override
public void loadMore() {
}
@Override
public void hintChange(String hint) {
headHint.setText(hint);
}
@Override
public void setWidthX(int x) {
}
});
我们还可以自定义添加头布局,头布局的话其实就是加载一个layout,用到什么就添加什么,我这里是自定义了一个弧形:
自定义弧形view:
/**
* Created by HXY on 2018/9/26.
* Be used for : 自定义首页头布局,弧形下拉效果
*/
public class HomeScrollview_HeadbgView extends View {
private int startdp;
private int width,height,length;
private Paint paint;
private Paint p;
private int widthX;
private boolean useX;
public HomeScrollview_HeadbgView(Context context) {
this(context,null);
}
public HomeScrollview_HeadbgView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public HomeScrollview_HeadbgView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(getResources().getColor(R.color.colorSilver_Transparent));
paint.setAntiAlias(true);
p = new Paint();
p.setColor(getResources().getColor(R.color.colorSilver_Transparent));
p.setStyle(Paint.Style.FILL);
startdp = UIUtil.dip2px(getContext(),50);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getWidth();
height = getHeight();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = getWidth();
height = getHeight();
}
@Override
protected void onDraw(Canvas canvas) {
Path mPath = new Path();
canvas.drawColor(getResources().getColor(R.color.colorSilver_Transparent));
if(height>startdp){
//高度达到一顶高度才显示弧线
mPath.moveTo(0, startdp);
// path的用法,时间问题,这里就不细说了
if(useX){ //判断有没有设置X,也就是setWidth(x)方法
mPath.rQuadTo(widthX, (height-startdp)*2, width, 0);
}else{
mPath.rQuadTo(width/2, (height-startdp)*2, width, 0);
}
canvas.drawPath(mPath, paint);
canvas.drawRect(0,0,width,startdp,paint); //填充空白
}else{
canvas.drawRect(0,0,width,height,paint);
}
}
public void setWidthX(int x){
useX = true;
widthX = x;
}
}
OK,搞完收工~