菜单

明白自定义视图

2019年2月14日 - 最新资讯

项目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) {
    // 滑动过程
}

}

代码分析

无异于看上去,那之中的代码是那多少个多的,可是也只是分为这几局部:

一,隐藏头布局(下拉刷新)和脚布局(加载愈来愈多)

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

3、在下拉和上拉的施行的时候,大家实施动画,更换头布局和脚步局里面的半空中(箭头、文字等)

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

这就是说大家就依据这几有些贰个3个的上课吧!

开辟界面的时候隐藏头布局(下拉刷新)

隐藏头布局的代码在偏下办法中:

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之中就可以了。

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

onTouch伊芙nt()触摸事件中的代码:

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地图