菜单

Android 精通自定义视图(5)

2018年10月11日 - 最新资讯

项目Demo:https://github.com/liaozhoubei/CustomViewDemo

下拉刷新和加载重多

下拉刷新以及加载重多就有限单力量在资讯类的app最易找到,当您想翻有无产生新型的新闻的时节偏偏需要将手指在屏幕及通往生一致拉就是会更新数据,而加载重多则是关到当前页面底部的上想找到往期的情,这时手指向上一样拉,往期情就加载出来。现在就给我们兑现这效果吧,效果如下:

最新资讯 1

Refreshlist.gif

贯彻原理

从成效图中分析,我们可汲取以下结论:

1、整体布局是用ListView实现之

2、上拉加载是在顶部追加数量,加载重多就是当底部多数据。

接下来我们于查看ListVew的详细资料,我们可见到它们富有addHeaderView()方法,将数据增长到顶部,同时也有着addFooterView()方法,将数据增长到脚。

现我们已经亮怎么当顶部及脚添加数据了,但是下拉的早晚发下拉属性的布局,上拉加载重多吗出一个搭架子,这又是怎么回事呢?

真正,这是一个深让人迷惑的地方,但是只要我们换一个思路,假而它本就是是在哪里,但是却为藏起来为?

答案却是是这么,ListView先设置头布局及顶部,但是也未在屏幕中形,只有在下拉的时候才显示出,同时以下拉的时节链接网络加载重多多少,这就是是落实加载重多的原理!

尽都分析到位了,现在我们尽管来看看代码是怎样落实的吧!

代码实现下拉刷新

则实现加载重多的效应是依靠让Listview,但是ListVew并不曾办法落实我们怀念使之所有力量,所以我们得以经过继承ListView来兑现我们要之效用。代码如下:

public class RefreshListView extends ListView implements OnScrollListener {

private View mHeaderView; // 头布局
private float downY; // 按下的y坐标
private float moveY; // 移动后的y坐标
private int mHeaderViewHeight; // 头布局高度
public static final int PULL_TO_REFRESH = 0;// 下拉刷新
public static final int RELEASE_REFRESH = 1;// 释放刷新
public static final int REFRESHING = 2; // 刷新中
private int currentState = PULL_TO_REFRESH; // 当前刷新模式
private RotateAnimation rotateUpAnim; // 箭头向上动画
private RotateAnimation rotateDownAnim; // 箭头向下动画
private View mArrowView; // 箭头布局
private TextView mTitleText; // 头布局标题
private ProgressBar pb; // 进度指示器
private TextView mLastRefreshTime; // 最后刷新时间
private OnRefreshListener mListener; // 刷新监听
private View mFooterView; // 脚布局
private int mFooterViewHeight; // 脚布局高度
private boolean isLoadingMore; // 是否正在加载更多

public RefreshListView(Context context) {
    super(context);
    init();
}

public RefreshListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

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

/**
 * 初始化头布局, 脚布局 滚动监听
 */
private void init() {
    initHeaderView();
    initAnimation();
    initFooterView();
    setOnScrollListener(this);
}

/**
 * 初始化脚布局
 */
private void initFooterView() {
    mFooterView = View.inflate(getContext(), R.layout.layout_footer_list, null);
    mFooterView.measure(0, 0);
    mFooterViewHeight = mFooterView.getMeasuredHeight();
    // 隐藏脚布局
    mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
    addFooterView(mFooterView);
}

/**
 * 初始化头布局的动画
 */
private void initAnimation() {
    // 向上转, 围绕着自己的中心, 逆时针旋转0 -> -180.
    rotateUpAnim = new RotateAnimation(0f, -180f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
            0.5f);
    rotateUpAnim.setDuration(300);
    rotateUpAnim.setFillAfter(true); // 动画停留在结束位置

    // 向下转, 围绕着自己的中心, 逆时针旋转 -180 -> -360
    rotateDownAnim = new RotateAnimation(-180f, -360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
            0.5f);
    rotateDownAnim.setDuration(300);
    rotateDownAnim.setFillAfter(true); // 动画停留在结束位置

}

/**
 * 初始化头布局
 */
private void initHeaderView() {

    mHeaderView = View.inflate(getContext(), R.layout.layout_header_list, null);
    mArrowView = mHeaderView.findViewById(R.id.iv_arrow);
    pb = (ProgressBar) mHeaderView.findViewById(R.id.pb);
    mTitleText = (TextView) mHeaderView.findViewById(R.id.tv_title);
    mLastRefreshTime = (TextView) mHeaderView.findViewById(R.id.tv_desc_last_refresh);

    // 提前手动测量宽高
    mHeaderView.measure(0, 0);// 按照设置的规则测量

    mHeaderViewHeight = mHeaderView.getMeasuredHeight();
    System.out.println(" measuredHeight: " + mHeaderViewHeight);

    // 设置内边距, 可以隐藏当前控件 , -自身高度
    mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);

    // 在设置数据适配器之前执行添加 头布局/脚布局 的方法.
    addHeaderView(mHeaderView);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {

    // 判断滑动距离, 给Header设置paddingTop
    switch (ev.getAction()) {
    case MotionEvent.ACTION_DOWN:
        downY = ev.getY();
        System.out.println("downY: " + downY);

        break;
    case MotionEvent.ACTION_MOVE:
        moveY = ev.getY();
        System.out.println("moveY: " + moveY);
        // 如果是正在刷新中, 就执行父类的处理
        if (currentState == REFRESHING) {
            return super.onTouchEvent(ev);
        }

        float offset = moveY - downY; // 移动的偏移量
        // 只有 偏移量>0, 并且当前第一个可见条目索引是0, 才放大头部
        if (offset > 0 && getFirstVisiblePosition() == 0) {

            // int paddingTop = -自身高度 + 偏移量

            int paddingTop = (int) (-mHeaderViewHeight + offset);
            mHeaderView.setPadding(0, paddingTop, 0, 0);

            if (paddingTop >= 0 && currentState != RELEASE_REFRESH) {// 头布局完全显示
                System.out.println("切换成释放刷新模式: " + paddingTop);
                // 切换成释放刷新模式
                currentState = RELEASE_REFRESH;
                updateHeader(); // 根据最新的状态值更新头布局内容
            } else if (paddingTop < 0 && currentState != PULL_TO_REFRESH) { // 头布局不完全显示
                System.out.println("切换成下拉刷新模式: " + paddingTop);
                // 切换成下拉刷新模式
                currentState = PULL_TO_REFRESH;
                updateHeader(); // 根据最新的状态值更新头布局内容
            }

            return true; // 当前事件被我们处理并消费
        }

        break;
    case MotionEvent.ACTION_UP:

        // 根据刚刚设置状态
        if (currentState == PULL_TO_REFRESH) {
            // - paddingTop < 0 不完全显示, 恢复
            mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
        } else if (currentState == RELEASE_REFRESH) {
            // - paddingTop >= 0 完全显示, 执行正在刷新...
            mHeaderView.setPadding(0, 0, 0, 0);
            currentState = REFRESHING;
            updateHeader();
        }
        break;

    default:
        break;
    }

    return super.onTouchEvent(ev);
}

/**
 * 根据状态更新头布局内容
 */
private void updateHeader() {
    switch (currentState) {
    case PULL_TO_REFRESH: // 切换回下拉刷新
        // 做动画, 改标题
        mArrowView.startAnimation(rotateDownAnim);
        mTitleText.setText("下拉刷新");

        break;
    case RELEASE_REFRESH: // 切换成释放刷新
        // 做动画, 改标题
        mArrowView.startAnimation(rotateUpAnim);
        mTitleText.setText("释放刷新");

        break;
    case REFRESHING: // 刷新中...
        mArrowView.clearAnimation();
        mArrowView.setVisibility(View.INVISIBLE);
        pb.setVisibility(View.VISIBLE);
        mTitleText.setText("正在刷新中...");

        if (mListener != null) {
            mListener.onRefresh(); // 通知调用者, 让其到网络加载更多数据.
        }

        break;

    default:
        break;
    }
}

/**
 * 刷新结束, 恢复界面效果
 */
public void onRefreshComplete() {
    if (isLoadingMore) {
        // 加载更多
        mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
        isLoadingMore = false;
    } else {
        // 下拉刷新
        currentState = PULL_TO_REFRESH;
        mTitleText.setText("下拉刷新"); // 切换文本
        mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);// 隐藏头布局
        pb.setVisibility(View.INVISIBLE);
        mArrowView.setVisibility(View.VISIBLE);
        String time = getTime();
        mLastRefreshTime.setText("最后刷新时间: " + time);
    }

}
// 将获取的时间格式化
private String getTime() {
    long currentTimeMillis = System.currentTimeMillis();
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return format.format(currentTimeMillis);
}

public interface OnRefreshListener {

    void onRefresh(); // 下拉刷新

    void onLoadMore();// 加载更多
}

public void setRefreshListener(OnRefreshListener mListener) {
    this.mListener = mListener;
}

// public static int SCROLL_STATE_IDLE = 0; // 空闲
// public static int SCROLL_STATE_TOUCH_SCROLL = 1; // 触摸滑动
// public static int SCROLL_STATE_FLING = 2; // 滑翔
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
    // 状态更新的时候
    System.out.println("scrollState: " + scrollState);
    if (isLoadingMore) {
        return; // 已经在加载更多.返回
    }

    // 最新状态是空闲状态, 并且当前界面显示了所有数据的最后一条. 加载更多
    if (scrollState == SCROLL_STATE_IDLE && getLastVisiblePosition() >= (getCount() - 1)) {
        isLoadingMore = true;
        System.out.println("scrollState: 开始加载更多");
        mFooterView.setPadding(0, 0, 0, 0);

        setSelection(getCount()); // 跳转到最后一条, 使其显示出加载更多.

        if (mListener != null) {
            mListener.onLoadMore();
        }
    }
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    // 滑动过程
}

}

代码分析

平等看上去,这其中的代码是杀多之,但是也只有是分为这几乎有:

1、隐藏头布局(下拉刷新)和下部布局(加载重多)

2、设置触摸事件,在下拉的上显得头布局,上拉的下显得脚布局

3、在下拉暨上拉底推行之时节,我们实行动画,更换头布局与脚步局里面的长空(箭头、文字等)

4、设置回掉方法,在下拉或上拉的时将数据传回到,同时更新ListView中的数码

那我们不怕以这几片一个一个之任课吧!

打开界面的早晚隐藏头布局(下拉刷新)

隐藏头布局之代码在偏下措施中:

private void initHeaderView() {

    mHeaderView = View.inflate(getContext(), R.layout.layout_header_list, null);
    mArrowView = mHeaderView.findViewById(R.id.iv_arrow);
    pb = (ProgressBar) mHeaderView.findViewById(R.id.pb);
    mTitleText = (TextView) mHeaderView.findViewById(R.id.tv_title);
    mLastRefreshTime = (TextView) mHeaderView.findViewById(R.id.tv_desc_last_refresh);

    // 提前手动测量宽高
    mHeaderView.measure(0, 0);// 按照设置的规则测量
    mHeaderViewHeight = mHeaderView.getMeasuredHeight();
    System.out.println(" measuredHeight: " + mHeaderViewHeight);

    // 设置内边距, 可以隐藏当前控件 , -自身高度
    mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);

    // 在设置数据适配器之前执行添加 头布局/脚布局 的方法.
    addHeaderView(mHeaderView);
}

由藏脚步局的不二法门和隐藏头布局之方式去不慌,这里就是着重讲解隐藏头布局之法子。

以头里我们询问及ListView中有addHeaderView()方法,将视图或者数额增长到ListVew的顶部,这里就是是始化头布局,然后拿其加载到顶部。重要之是当下三实行代码:

    mHeaderView.measure(0, 0);// 按照设置的规则测量
    mHeaderViewHeight = mHeaderView.getMeasuredHeight();        
    mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);

何以要使measure()方法,这是因ListView是当Activity执行setContentView()加载了布局之后才后高度和宽。但是我们于起定义控件被设超前收获控件里面的子布局的可观和宽窄,而在measure()传入0这是代表以原有的,在XML中设定好之大大小小获取宽高。

然后便是setPadding()方法,这个方法是免是充分熟稔,Padding是安布局的内边距。ListVew的顶部的去为0,也尽管是正在屏幕的顶部,而以mHeaderView头布局的Top顶部的比方设置也负数,那么mHeaderView将不见面在屏幕被形出来,大家可以协调新建一个列试试Pading的功效。

以上就是是隐藏头布局之代码,然后拿隐形好的峰布局直接助长到LisView之中就可以了。

设置触摸事件,在下拉的上显得头布局

onTouchEvent()触摸事件中之代码:

switch (ev.getAction()) {
    case MotionEvent.ACTION_DOWN:
        downY = ev.getY();
        break;
    case MotionEvent.ACTION_MOVE:
        moveY = ev.getY();
        // 如果当前已经在下拉刷新状态,那么就不刷新
        if (currentState == REFRESHING) {
            return super.onTouchEvent(ev);
        }
        float offset = moveY - downY; // 移动的偏移量
        if (offset > 0 && getFirstVisiblePosition() == 0) {
            int paddingTop = (int) (-mHeaderViewHeight + offset);
            mHeaderView.setPadding(0, paddingTop, 0, 0);
            if (paddingTop >= 0 && currentState != RELEASE_REFRESH) {// 头布局完全显示
                currentState = RELEASE_REFRESH;
                updateHeader(); // 根据最新的状态值更新头布局内容
            } else if (paddingTop < 0 && currentState != PULL_TO_REFRESH) { // 头布局不完全显示
                currentState = PULL_TO_REFRESH;
                updateHeader(); // 根据最新的状态值更新头布局内容
            }
            return true; // 当前事件被我们处理并消费
        }
        break;
    case MotionEvent.ACTION_UP:
        if (currentState == PULL_TO_REFRESH) {
            mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
        } else if (currentState == RELEASE_REFRESH) {
            mHeaderView.setPadding(0, 0, 0, 0);
            currentState = REFRESHING;
            updateHeader();
        }
        break;

    default:
        break;
    }

于这段代码中,主要是装mHeaderView的setPadding()方法,在脚下ListView条目为顶部率先长长的时就是用其PaddingTop内边距顶部的离开不断的回落,依据手指运动的去从负数转为正数,将mHeaderView显示出。

在下拉的时节显得动画

每当触摸事件中出updateHeader()方法,这是因此来推行选择执行动画及转换文字的一个主意,当我们下去头布局,头布局了显示的时候该下箭头变为上箭头,同时更换文字;在放出下拉,手指离开屏幕的早晚,箭头消失,变成循环速度长条

设置回调方法,更新ListView中的数目

最终就是回调方法了,在此地开创了一个Interface接口类OnRefreshListener,这之中来少单办法,一个凡生拉刷新时长数据,一个凡是加载重多时加多少。

斯类似在setRefreshListener中受采取,但咱的RefreshListView自定义控件被使用时,调用setRefreshListener方法,这是将重复写OnRefreshListener接口的少数个点子,在其中传播要加进的数额,并且更新适配器,这时RefreshListView就能实时更新数据了。

总结

下拉刷新的自定义视图就说到此就截止了,我们的精通自定义视图也到这个就是收了。

则身为精通自定义视图,但说实话这只有是应用层的自定义视图的行使,仍然是基于Android提供的框架中改,如果的确想创立有属于自己之自定义视图,恐怕需要进入Android底层或者框架层写代码吧!

只是不管怎么样,学习完这几乎独从定义视图,相信于应用层这面的自定义视图都非会见起极端怪的窘迫了。

以此间的几乎首文章被,提纲挈领的说了每个品种里面比较主要的章程,关于切实的逻辑并不曾详解,所以这上头需要大家多看代码,又或直接敲代码,这样才会明白这其间说涵盖的学问。

项目Demo:https://github.com/liaozhoubei/CustomViewDemo

扩展阅读:

Android 精通自定义视图最新资讯(1)
http://www.jianshu.com/p/c2195269ce44

Android 精通自定义视图(2)
http://www.jianshu.com/p/092e126b623f

Android 精通自定义视图(3)
http://www.jianshu.com/p/1660479e76ef

Android 精通自定义视图(4)
http://www.jianshu.com/p/850e387fc9d8

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图