猫眼电影下拉刷新效果实现

无入侵的猫眼下拉刷新,无需重写RecyclerView,像SwipeRefreshLayout一样使用简单

最近在做一个高仿的猫眼App,观察猫眼电影App的下拉刷新后,发现其中还是有小细节的,所以决定自己动手撸了一个猫眼下拉刷新(其实是网上实在没有找到可以抄的代码。。),结合SuperRefreshLayout加上自定义的一个刷新头部达到了猫眼App的下拉刷新效果


先上图

file.gif

效果和正版基本上是一样的,接下来贴出实现方法和思路
由于是基于SuperRefreshLayout重写的下拉刷新的接口实现,所以原有的功能都会有(无入侵,支持RecyclerView,ScrollView,etc)

下拉刷新几个阶段:

1.下拉过程中,达到一定的距离后红圈开始逐渐显示
2.还未达到可以刷新的距离时向上滑动,红圈会逐渐消失
3.当达到刷新距离时,松手红圈开始转动

需要编写的几个类

1.刷新的头部(红圈和灰色猫眼icon)
2.实现SuperSwipeRefreshLayout.OnPullRefreshListener 接口

先看头部View

主要实现在onDraw方法

public class RefreshView extends ImageView {

private Paint mPaint;
private float progress;//进度,就是显示的程度
private boolean isAnimate;//判断是否在旋转
private int rotateProgress;//旋转角度
private Handler mHandler;

public RefreshView(Context context) {
    this(context, null);
}

public RefreshView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public RefreshView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
}

private void init() {
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    rotateProgress = 0;
    progress = 0f;
    mHandler = new Handler();
}

@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    int minWidth = (int) (this.getWidth() * progress);
    int minHeight = (int) (this.getHeight() * progress);
    //开始滑动的时候才进行绘制
    if (minWidth > 1 && minHeight > 1) {

        Bitmap inner = BitmapFactory.decodeResource(getResources(), R.drawable.bg_pull_process);
        Bitmap circle = BitmapFactory.decodeResource(getResources(), R.drawable.ic_progress_out);

        float scaleW = (float) getWidth() / (float) inner.getWidth();
        float scaleH = (float) getHeight() / (float) inner.getHeight();
        int scaleWidth = (int) (inner.getWidth() * scaleW);
        int scaleHeight = (int) (inner.getHeight() * scaleH);
        //画内部的灰色猫icon,canvas画图,大小根据图片的
        canvas.drawBitmap(inner, null, new Rect(0, 0, scaleWidth, scaleHeight), mPaint);

        Matrix matrix = new Matrix();
        //顺时针旋转180度,因为一开始应该是从下网上画图片
        matrix.postRotate(rotateProgress, (float) scaleWidth / 2.0f, (float) scaleHeight / 2.0f);
        //创建一个新的可缩放的bitmap
        Bitmap temp_circle = Bitmap.createScaledBitmap(circle, scaleWidth, scaleHeight, true);

        //进度最大为1f,超过设为1f
        if (progress >= 1.0f) {
            progress = 1.0f;
        }

       //创建一个bitmap,其实就是从下到上画一个图,
        // progress每次变动都会重新绘制外圈, 
        // createBitmap最后一个参数就是所画图片的高度,
        // 因为是根据显示程度来绘制,所以高度*(progress) 其中progess<=1,就会有渐变的效果

        Bitmap mask_outter_circle = Bitmap.createBitmap(temp_circle, 0, 0,
                temp_circle.getWidth(), progress == 1.0f ?
                        temp_circle.getHeight()
                        : (int) (temp_circle.getHeight() * progress));
        //画红圈
        canvas.drawBitmap(mask_outter_circle, matrix, mPaint);

        inner.recycle();
        temp_circle.recycle();
        mask_outter_circle.recycle();
        circle.recycle();
    }
}

/**
 * 设置红色圆圈显示程度
 */
public void setProgress(float progress) {
    this.progress = progress;
    this.invalidate();
}

/**
 * 开始旋转动画
 */
public void startAnimate() {
    if (!isAnimate) {
        isAnimate = true;
        mHandler.post(mRunnable);
    }

}

/**
 * 结束旋转动画
 */
public void stopAnimate() {
    isAnimate = false;
    mHandler.removeCallbacks(mRunnable);
    rotateProgress = 180;
    progress = 0f;
}

private Runnable mRunnable = new Runnable() {

    @Override
    public void run() {
        rotateProgress += 8;
        if (isAnimate) {
            mHandler.postDelayed(mRunnable, 10);
        }
        RefreshView.this.invalidate();
    }
};  }

实现SuperSwipeRefreshLayout.OnPullRefreshListener,完成下拉刷新的逻辑

`

public class MyPullToRefreshListener implements  SuperSwipeRefreshLayout.OnPullRefreshListener {

private OnRefreshListener mOnRefreshListener;
private boolean isRefresh;//是否可以刷新
private int mScrollDistance;//滑动距离
private RefreshView refreshView;//刷新的头部
private int height;//头部高度
private SuperSwipeRefreshLayout mSwipeRefreshLayout;
private final int scrollPx;//需要滑动的距离


public MyPullToRefreshListener(Context context, SuperSwipeRefreshLayout superSwipeRefreshLayout) {

    this.mSwipeRefreshLayout = superSwipeRefreshLayout;
    setupRefreshView(context);
    scrollPx = UiUtils.dp2px(context, 25);
}


private void setupRefreshView(Context context) {
    refreshView = new RefreshView(context);
    mSwipeRefreshLayout.setHeaderView(refreshView);
    height = mSwipeRefreshLayout.getHeaderHeight();
}

/**
 * 加载成功之后回调
 */
public void refreshDone() {
    //RxJava的延迟操作700毫秒停止动画
    Observable.timer(700, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
            .map(new Func1<Long, Void>() {
                @Override
                public Void call(Long aLong) {
                    //停止旋转
                    refreshView.stopAnimate();
                    //刷新结束
                    mSwipeRefreshLayout.setRefreshing(false);
                    return null;
                }
            })
            .subscribe();
}
/**
 * 刷新操作
 */
@Override
  public void onRefresh() {
    if (mOnRefreshListener != null) {
        mOnRefreshListener.refresh();
        refreshView.startAnimate();
    }
}

//根据滑动的距离来对View的进行绘制
@Override
public void onPullDistance(int distance) {
    if (mScrollDistance > distance) {
        
        //当不在刷新状态并且滑动距离超过scrollPx之后才开始变色
        if (!isRefresh && distance > scrollPx) {
        // Log.d("滑动", "onPullDistance: 向上变颜色");
        //渐变程度,其实就是一个线性渐变的函数,y/x = scale;
        //由于需要滑动到一定的程度再进行渐变,
        // 函数为 (y-scrollPx)/x = scale------->(y-scrollPx)/x = y/x  
        //其中y = 滑动距离,x = 刷新布局的高度
            float scale = ((float) distance - scrollPx) / (((2 * height * scrollPx) / distance));
            refreshView.setProgress(scale <= 0f ? 0f : scale);
        }
    } else {
        if (!isRefresh && distance > scrollPx) {
            float scale = ((float) distance - scrollPx) / (((2 * height * scrollPx) / distance));
            refreshView.setProgress(scale > 1f ? 1f : scale);
        //  Log.d("滑动", "onPullDistance: 向下变颜色" + distance + "---" + height + "---scale:" + scale);
        }
    }
    mScrollDistance = distance;
}

@Override
public void onPullEnable(boolean enable) {
    isRefresh = enable;
}

public void setOnRefreshListener(OnRefreshListener OnRefreshListener) {
    this.mOnRefreshListener = OnRefreshListener;
}

/**
 * 刷新回调,在该方法中执行刷新数据操作
 */
public interface OnRefreshListener {
    void refresh();
}}

使用方法

XML包裹需要下拉刷新的控件

Paste_Image.png

Activity/Fragment代码

    MyPullToRefreshListener pullToRefreshListener = new  MyPullToRefreshListener(mContext,swip);
    pullToRefreshListener.setOnRefreshListener(new MyPullToRefreshListener.OnRefreshListener() {
        @Override
        public void refresh() {
           //实现你的刷新功能
        }
    });
    swip.setOnPullRefreshListener(pullToRefreshListener);
    
    //刷新成功或者失败后调用
    pullToRefreshListener.refreshDone();

最后,语文学的差,表达能力欠佳,写了这么多也不知道自己表达清楚没有,但是效果还是出来了,第一次写简书,恳请各位包容。

项目是持续在更新的高仿猫眼电影,没有单独的Demo,如果有需要请留言,会单独开一个工程Demo

项目地址:https://github.com/Cicinnus0407/CatEye

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,142评论 25 707
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,665评论 22 664
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,952评论 4 60
  • 昨天的经历让我充满了挫败感。昨天从早到晚,可能都不止8个小时,仅仅写出了一个Hello 这样的程序。事情的经过是这...
    newuer阅读 276评论 0 0
  • 今天早早起来就去食堂吃了早饭 这学期在学校食堂的第一顿早饭也是最后一顿 总要走的 每个男的都会有处...
    应小涛阅读 267评论 0 1