Android View - 上拉刷新下拉加载ListView

Android | 同时实现上拉刷新和下拉加载 | 基于SwipeRefreshLayout 和 ListView 文章目录实现效果1. 准备环境2. 布局2.1 foot_view2.2 activity_main3.代码实现3.1 全局变量3.2 OnCreate3.3 自定义的showInfo方法3.4 下拉刷新3.5 上拉加载4. 所有代码 实现效果 下拉刷新 上拉加载 1. 准备环境 Android Studio 2020.3.1 (1) 给项目安装 androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-beta01布局组件 (2) 在 depen 阅读详情

虽然网上有很多上拉刷新库,效果也很好,只是有时候突然来的Bug不是很好处理,而且有时候还达不到效果,所以今天用ListView实现上拉刷新,下拉加载的效果。直接上码:

本着易扩展的理念,先写一个父类,实现上拉刷新,下拉加载的效果:

package com.johan.library.viewtoolkit.refreshlistview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;

/**
 * Created by johan on 2017/7/27.
 */

public abstract class AbsRefreshListView extends ListView implements AbsListView.OnScrollListener {

    /** 动画每毫秒移动距离 */
    private static final int ANIMATION_STEP = 1;

    /** 移动速率 */
    private static final float MOVE_RATIO = 0.4f;

    /** 刷新View最大高度 */
    private static final int REFRESH_VIEW_MAX_HEIGHT = 500;

    /** 不没有刷新 */
    private static final int STATE_NO_REFRESH = 1;
    /** 正在上拉或者下拉 */
    private static final int STATE_RELEASE_REFRESH = 2;
    /** 正在刷新 */
    private static final int STATE_REFRESHING = 3;

    /** 状态 */
    private int state = STATE_NO_REFRESH;

    /** 记录手指滑动的Y值 */
    private float currentY;

    /** headerView和footerView的原始值 */
    private int headerHeight, footerHeight;

    /** Header Footer View */
    private View headerView, footerView;

    /** 记录是否最顶和最底 */
    private boolean isTop = true, isBottom;

    /** 监听滑动的Listener,因为在AbsRefreshListView已经调用了setOnScrollListener,需要以另一种方式提供用户使用 */
    private OnScrollListener onScrollListener;

    /** 是否可以上拉和下拉 */
    private boolean canPullTop = true, canPullBottom = true;

    /** 判断是否在动画 */
    private boolean isAnimation;

    /** 记录动画时,上一次的值 */
    private int lastAnimationValue;

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

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

    /**
     * 初始化
     */
    private void init() {
        headerView = initHeaderView();
        footerView = initFooterView();
        if (headerView != null) {
            // 获取headerView的高度
            headerView.measure(0, 0);
            headerHeight = headerView.getMeasuredHeight();
            post(new Runnable() {
                @Override
                public void run() {
                    // 设置headerView的高度
                    setViewHeight(headerView, 0);
                }
            });
            addHeaderView(headerView);
        } else {
            canPullTop = false;
        }
        if (footerView != null) {
            // 获取footerView的高度
            footerView.measure(0, 0);
            footerHeight = footerView.getMeasuredHeight();
            post(new Runnable() {
                @Override
                public void run() {
                    // 设置footerView的高度
                    setViewHeight(footerView, 0);
                }
            });
            addFooterView(footerView);
        } else {
            canPullBottom = false;
        }
        setOnScrollListener(this);
    }

    /**
     * 设置View的高度
     * Bug1 : 当height=0时,view会恢复原来的高度
     * Bug1解决办法 : 如果height=0时,隐藏view,并设为1
     * Bug2 : 当height=0时,分割线还会显示
     * Bug2解决办法 : 如果height=0时,隐藏分割线,还好ListView有设置是否显示分割线的方法
     * @param view
     * @param height
     */
    private void setViewHeight(View view, int height) {
        int visibility = height == 0 ? View.GONE : View.VISIBLE;
        view.setVisibility(visibility);
        boolean isShowDivider = height != 0;
        if (view == headerView) {
            setHeaderDividersEnabled(isShowDivider);
        } else {
            setFooterDividersEnabled(isShowDivider);
        }
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (params == null) {
            params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        }
        height = height == 0 ? 1 : height;
        params.height = height;
        view.setLayoutParams(params);
    }

    /**
     * 初始化Header,子类实现
     * @return
     */
    protected abstract View initHeaderView();

    /**
     * 初始化Footer,子类实现
     * @return
     */
    protected abstract View initFooterView();

    /**
     * 重写onTouchEvent实现上拉和下拉功能
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isAnimation) {
            return super.onTouchEvent(event);
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN :
                currentY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE :
                // 单次移动的距离:手指移动的距离乘以移动速率,为了不让刷新的View显示太快
                float moveY = (event.getY() - currentY) * MOVE_RATIO;
                currentY = event.getY();
                // 上拉
                if (isTop && canPullTop) {
                    // 在最顶,如果上滑,不拦截
                    if (state == STATE_NO_REFRESH && moveY < 0) return super.onTouchEvent(event);
                    // 设置headerView高度
                    updateHeader((int) moveY);
                    // 如果不处理的话,下拉至刷新状态之后,不放手,然后上滑,发现设置不了headerView的高度
                    // 因为此时的ListView也会处理ACTION_MOVE事件下滑,所以我们要返回true,表示我们要处理事件
                    return true;
                }
                // 下拉
                if (isBottom && canPullBottom) {
                    // 在最底,如果下滑,不拦截
                    if (state == STATE_NO_REFRESH && moveY > 0) return super.onTouchEvent(event);
                    // 设置footerView高度
                    updateFooter((int) -moveY);
                    // 因为我们处理事件,所以ListView的滑动就停止了
                    // 所以,虽然我们设置了footerView的高度,但是还是显示不出来
                    // 因此,需要我们手动设置滑动
                    scrollBy(0, (int) -moveY);
                    // 表示处理事件
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP :
                currentY = 0;
                if (state == STATE_RELEASE_REFRESH) {
                    if (headerView.getHeight() >= headerHeight) {
                        // 上拉刷新
                        releaseRefresh();
                        state = STATE_REFRESHING;
                        onRefreshing(true);
                    } else if (footerView.getHeight() >= footerHeight) {
                        // 下拉加载
                        releaseRefresh();
                        state = STATE_REFRESHING;
                        onRefreshing(false);
                    } else {
                        // 没有刷新的话,回到原始状态
                        completeRefresh();
                    }
                }
                break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 更新headerView
     * @param moveY
     */
    private void updateHeader(int moveY) {
        state = STATE_RELEASE_REFRESH;
        int height = headerView.getHeight() + moveY;
        height = Math.max(0, Math.min(REFRESH_VIEW_MAX_HEIGHT, height));
        setViewHeight(headerView, height);
        onProgress(true, height, headerHeight);
    }

    /**
     * 更新footerView
     * @param moveY
     */
    private void updateFooter(int moveY) {
        state = STATE_RELEASE_REFRESH;
        int height = footerView.getHeight() + moveY;
        height = Math.max(0, Math.min(REFRESH_VIEW_MAX_HEIGHT, height));
        setViewHeight(footerView, height);
        onProgress(false, footerView.getHeight(), footerHeight);
    }

    /**
     * 松手刷新
     */
    private void releaseRefresh() {
        final View refreshView = headerView.getHeight() > 1 ? headerView : footerView.getHeight() > 1 ? footerView : null;
        int refreshViewHeight = headerView.getHeight() > 1 ? headerHeight : footerView.getHeight() > 1 ? footerHeight : 0;
        if (refreshView == null) {
            return;
        }
        if (refreshViewHeight == 0) {
            return;
        }
        if (refreshView.getHeight() < 2) {
            return;
        }
        if (refreshView.getHeight() > refreshViewHeight) {
            lastAnimationValue = refreshView.getHeight();
            ValueAnimator animator = ValueAnimator.ofInt(refreshView.getHeight(), refreshViewHeight);
            animator.setDuration((refreshView.getHeight() - refreshViewHeight) / ANIMATION_STEP + 1);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    int height = (int) animation.getAnimatedValue();
                    setViewHeight(refreshView, height);
                    // 因为上拉加载时,我们手动滚动了ListView,现在要恢复滚动的位置
                    if (isBottom) {
                        scrollBy(0, height - lastAnimationValue);
                        lastAnimationValue = height;
                    }
                }
            });
            animator.start();
        }
    }

    /**
     * 完成刷新
     * Bug1 : 手指正在滑动,打算取消刷新,而此时,外部调用了completeRefresh完成刷新,把refreshView重置了,
     *        造成手指还在滑动,refreshView突然不见
     * Bug1解决办法 : 根据 currentY != 0 判断手指是否在滑动,如果手指还在滑动,则不做任何操作,否则重置refreshView
     * 注意1 : 不能用 headerView.getHeight() != 0 这种方式判断是否显示有headerView,具体原因请看setViewHeight方法
     *         要用 headerView.getHeight() > 1 方式判断
     */
    public void completeRefresh() {
        if (currentY != 0) return;
        isAnimation = true;
        final View refreshView = headerView.getHeight() > 1 ? headerView : footerView.getHeight() > 1 ? footerView : null;
        if (refreshView == null) {
            isAnimation = false;
            return;
        }
        if (refreshView.getHeight() < 2) {
            isAnimation = false;
            return;
        }
        lastAnimationValue = refreshView.getHeight();
        ValueAnimator animator = ValueAnimator.ofInt(refreshView.getHeight(), 0);
        animator.setDuration(refreshView.getHeight() / ANIMATION_STEP + 1);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int height = (int) animation.getAnimatedValue();
                setViewHeight(refreshView, height);
                // 因为上拉加载时,我们手动滚动了ListView,现在要恢复滚动的位置
                if (isBottom) {
                    scrollBy(0, height - lastAnimationValue);
                    lastAnimationValue = height;
                }
                if (height == 0) {
                    state = STATE_NO_REFRESH;
                    isAnimation = false;
                    onComplete(refreshView == headerView);
                }
            }
        });
        animator.start();
    }

    /**
     * 判断是否最顶或者最低或者都不是
     * @param view
     * @param firstVisibleItem
     * @param visibleItemCount
     * @param totalItemCount
     */
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (firstVisibleItem == 0) {
            isTop = true;
            isBottom = false;
        } else if ((firstVisibleItem + visibleItemCount) == totalItemCount) {
            isBottom = true;
            isTop = false;
        } else {
            isTop = false;
            isBottom = false;
        }
        if (onScrollListener != null) {
            onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (onScrollListener != null) {
            onScrollListener.onScrollStateChanged(view, scrollState);
        }
    }

    /**
     * 设置是否可以上拉
     * @param canPullTop
     */
    public void setCanPullTop(boolean canPullTop) {
        this.canPullTop = canPullTop;
    }

    /**
     * 设置是否可以下拉
     * @param canPullBottom
     */
    public void setCanPullBottom(boolean canPullBottom) {
        this.canPullBottom = canPullBottom;
    }

    /**
     * 是否在刷新
     * @return
     */
    public boolean isRefreshing() {
        return state == STATE_REFRESHING;
    }

    /**
     * 因为AbsRefreshListView已经调用了setOnScrollListener方法,不能在外面调用setOnScrollListener,否则会影响AbsRefreshListView
     * 请使用该方法,和setOnScrollListener的功能是一样的
     * @param onScrollListener
     */
    public void setScrollListener(OnScrollListener onScrollListener) {
        this.onScrollListener = onScrollListener;
    }

    /**
     * 下拉上拉时调用
     * @param isTop
     * @param currentHeight
     * @param viewHeight
     */
    protected void onProgress(boolean isTop, int currentHeight, int viewHeight) {

    }

    /**
     * 刷新时调用,子类实现
     * @param isTop
     */
    protected abstract void onRefreshing(boolean isTop);

    /**
     * 刷新完成时调用
     * @param isTop
     */
    protected void onComplete(boolean isTop) {

    }

}

代码的注释已经很清楚说明了上拉刷新和下拉加载的原理,还有遇到的一些坑和解决办法,相信你看得懂。
下面是我实现的子类:

package com.johan.library.viewtoolkit.refreshlistview;

import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.TextView;

import com.johan.library.R;

/**
 * Created by johan on 2017/7/27.
 */

public class RefreshListView extends AbsRefreshListView {

    private ImageView headerIconView, footerIconView;
    private TextView headerContentView, footerContentView;

    private RefreshMessage refreshMessage;

    private ObjectAnimator rotateAnimator;

    private OnRefreshListener onRefreshListener = new OnRefreshListener() {
        @Override
        public void onPullRefreshing() {}
        @Override
        public void onLoadRefreshing() {}
    };

    public RefreshListView(Context context) {
        super(context);
        refreshMessage = new RefreshMessage();
        initView();
    }

    public RefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 支持attr自定义属性
        refreshMessage = new RefreshMessage();
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RefreshListView);
        if (array.hasValue(R.styleable.RefreshListView_refresh_pull_icon)) {
            refreshMessage.pullIcon = array.getDrawable(R.styleable.RefreshListView_refresh_pull_icon);
        }
        if (array.hasValue(R.styleable.RefreshListView_refresh_pull_tip)) {
            refreshMessage.pullTip = array.getString(R.styleable.RefreshListView_refresh_pull_tip);
        }
        if (array.hasValue(R.styleable.RefreshListView_refresh_pull_release_refresh_tip)) {
            refreshMessage.pullReleaseRefreshTip = array.getString(R.styleable.RefreshListView_refresh_pull_release_refresh_tip);
        }
        if (array.hasValue(R.styleable.RefreshListView_refresh_pull_refreshing_tip)) {
            refreshMessage.pullRefreshingTip = array.getString(R.styleable.RefreshListView_refresh_pull_refreshing_tip);
        }
        if (array.hasValue(R.styleable.RefreshListView_refresh_load_icon)) {
            refreshMessage.loadIcon = array.getDrawable(R.styleable.RefreshListView_refresh_load_icon);
        }
        if (array.hasValue(R.styleable.RefreshListView_refresh_load_tip)) {
            refreshMessage.loadTip = array.getString(R.styleable.RefreshListView_refresh_load_tip);
        }
        if (array.hasValue(R.styleable.RefreshListView_refresh_load_release_refresh_tip)) {
            refreshMessage.loadReleaseRefreshTip = array.getString(R.styleable.RefreshListView_refresh_load_release_refresh_tip);
        }
        if (array.hasValue(R.styleable.RefreshListView_refresh_load_refreshing_tip)) {
            refreshMessage.loadRefreshingTip = array.getString(R.styleable.RefreshListView_refresh_load_refreshing_tip);
        }
        array.recycle();
        initView();
    }

    @Override
    protected View initHeaderView() {
        View headerView = LayoutInflater.from(getContext()).inflate(R.layout.header_default, null);
        headerIconView = (ImageView) headerView.findViewById(R.id.header_default_icon);
        headerContentView = (TextView) headerView.findViewById(R.id.header_default_content);
        return headerView;
    }

    @Override
    protected View initFooterView() {
        View footerView = LayoutInflater.from(getContext()).inflate(R.layout.footer_default, null);
        footerIconView = (ImageView) footerView.findViewById(R.id.footer_default_icon);
        footerContentView = (TextView) footerView.findViewById(R.id.footer_default_content);
        return footerView;
    }

    private void initView() {
        if (refreshMessage.pullIcon != null) {
            headerIconView.setImageDrawable(refreshMessage.pullIcon);
        }
        headerContentView.setText(refreshMessage.pullTip);
        if (refreshMessage.loadIcon != null) {
            footerIconView.setImageDrawable(refreshMessage.loadIcon);
        }
        footerContentView.setText(refreshMessage.loadTip);
    }

    @Override
    protected void onRefreshing(boolean isTop) {
        View refreshView = isTop ? headerIconView : footerIconView;
        startRotateAnimation(refreshView);
        if (isTop) {
            onRefreshListener.onPullRefreshing();
            headerContentView.setText(refreshMessage.pullRefreshingTip);
        } else {
            onRefreshListener.onLoadRefreshing();
            footerContentView.setText(refreshMessage.loadRefreshingTip);
        }
    }

    @Override
    protected void onProgress(boolean isTop, int currentHeight, int viewHeight) {
        if (isTop) {
            String headerContent = currentHeight >= viewHeight ? refreshMessage.pullReleaseRefreshTip : refreshMessage.pullTip;
            headerContentView.setText(headerContent);
            rotateIcon(headerIconView, 360 * (currentHeight % viewHeight) / viewHeight);
        } else {
            String footerContent = currentHeight >= viewHeight ? refreshMessage.loadReleaseRefreshTip : refreshMessage.loadTip;
            footerContentView.setText(footerContent);
            rotateIcon(footerIconView, 360 * (currentHeight % viewHeight) / viewHeight);
        }
        endRotateAnimation();
    }

    @Override
    protected void onComplete(boolean isTop) {
        endRotateAnimation();
    }

    private void startRotateAnimation(View view) {
        rotateAnimator = ObjectAnimator.ofFloat(view, "rotation", 0, 359.0f);
        rotateAnimator.setDuration(500);
        rotateAnimator.setRepeatCount(-1);
        rotateAnimator.setInterpolator(new LinearInterpolator());
        rotateAnimator.start();
    }

    private void endRotateAnimation() {
        if (rotateAnimator != null) {
            rotateAnimator.end();
            rotateAnimator = null;
        }
    }

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

    public interface OnRefreshListener {
        void onPullRefreshing();
        void onLoadRefreshing();
    }

    private void rotateIcon(ImageView imageView, int angle) {
        imageView.setPivotX(imageView.getWidth() / 2);
        imageView.setPivotY(imageView.getHeight() / 2);
        imageView.setRotation(angle);
    }

    public void setPullTip(String pullTip) {
        refreshMessage.pullTip = pullTip;
    }

    public void setPullReleaseRefreshTip(String pullReleaseRefreshTip) {
        refreshMessage.pullReleaseRefreshTip = pullReleaseRefreshTip;
    }

    public void setPullRefreshingTip(String pullRefreshingTip) {
        refreshMessage.pullRefreshingTip = pullRefreshingTip;
    }

    public void setPullIcon(int pullIcon) {
        headerIconView.setImageResource(pullIcon);
    }

    public void setLoadTip(String loadTip) {
        refreshMessage.loadTip = loadTip;
    }

    public void setLoadReleaseRefreshTip(String loadReleaseRefreshTip) {
        refreshMessage.loadReleaseRefreshTip = loadReleaseRefreshTip;
    }

    public void setLoadRefreshingTip(String loadRefreshingTip) {
        refreshMessage.loadRefreshingTip = loadRefreshingTip;
    }

    public void setLoadIcon(int loadIcon) {
        footerIconView.setImageResource(loadIcon);
    }

    class RefreshMessage {
        public String pullTip = "下拉刷新";
        public String pullReleaseRefreshTip = "松手刷新";
        public String pullRefreshingTip = "正在刷新";
        public Drawable pullIcon = null;
        public String loadTip = "上拉加载";
        public String loadReleaseRefreshTip = "松手加载";
        public String loadRefreshingTip = "正在加载";
        public Drawable loadIcon = null;
    }

}

只要模仿RefreshListView,继承AbsRefreshListView,就很轻松实现自己的上拉刷新下拉加载的ListView。

代码已经上传到我的github:https://github.com/JohanMan/viewtoolkit

因为RefreshListView是已经实现了上拉刷新下拉加载了,可以直接使用。

使用实例:

xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:refresh_view="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.johan.viewtoolkit.MainActivity">

    <com.johan.library.viewtoolkit.refreshlistview.RefreshListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:dividerHeight="1px"
        android:divider="#cccccc"
        refresh_view:refresh_pull_icon="@drawable/my_refresh_icon"
        refresh_view:refresh_pull_tip="下拉刷新哦"
        refresh_view:refresh_pull_release_refresh_tip="松手刷新哦"
        refresh_view:refresh_pull_refreshing_tip="正在刷新哈"
        />

</LinearLayout>

activity

final RefreshListView listView = (RefreshListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
listView.setLoadIcon(R.drawable.my_refresh_icon);
listView.setLoadTip("上拉加载哦");
listView.setOnRefreshListener(new RefreshListView.OnRefreshListener() {
    @Override
    public void onPullRefreshing() {
        // 模拟延时3秒
        listView.postDelayed(new Runnable() {
            @Override
            public void run() {
                listView.completeRefresh();
            }
        }, 3000);
    }
    @Override
    public void onLoadRefreshing() {
        // 模拟延时3秒
        listView.postDelayed(new Runnable() {
            @Override
            public void run() {
                listView.completeRefresh();
            }
        }, 3000);
    }
});
Android scrollview中嵌套listview实现listview的下拉刷新上拉加载更多 在Android中 scrollview中嵌套listview实现listview的下拉刷新上拉加载更多 阅读详情

相关推荐

Android使用PullToRefresh完成ListView下拉刷新和左滑删除

吹在前面的话:ListView下刷新刷功能相信从事Android开发的猿友们并不陌生,包括现在Google亲儿子SwipeRefreshLayout实现效果在一些APP上也能看见(不过个人不喜欢官方的刷新效果)。本文就带领一些刚入门android的朋友或者一起爱分享的朋友来简单的实现ListView的下拉刷新和左滑删除效果。一、本文主要内容: 使用PullToRefresh完成ListView下拉、

一名互联网时代苟且偷生的 Android码农 4953

自定义RecyclerView实现下拉刷新和上拉加载

通过导入第三方库实现ListView的上拉加载和下拉刷新比较简单,今天我要讲的是自定义RecyclerView实现下拉刷新和上拉加载。首先,自定义下拉刷新上拉加载是通过给RecyclerView添加头部和尾部实现的。而问题是RecyclerView并没有addHeaderView(View v)和addFooterView(View v)方法。 第一步:自定义HeaderAndFo

xiaoyantan的博客 8697

Android RecyclerView实现下拉刷新和上拉加载更多功能

为了提供更好的用户体验,我们常常需要在RecyclerView中实现下拉刷新和上拉加载更多的功能。为了实现这个功能,我们需要自定义一个RecyclerView的OnScrollListener,并在滑动到底部时触发加载更多的操作。综上所述,通过自定义的OnScrollListener,我们可以很方便地实现RecyclerView的下拉刷新和上拉加载更多功能。方法中,我们可以处理加载更多的逻辑,比如请求下一页的数据。方法中,我们可以处理下拉刷新的逻辑,比如重新加载数据。在加载完成后,记得调用。

DevWizard的博客 929

android studio上拉加载,AndroidStudio的PullToRefreshListView简单使用

PullToRefreshListView的下载:https://github.com/chrisbanes/Android-PullToRefresh1.先将其中的library倒入到Module,然后与App进行关联2.代码,我并不是将PullToRefreshListView直接写到xml中,而是创建成对象。PullToRefreshListViewpullToRefreshListVie...

weixin_39856589的博客 377

Android——使用自定义OnScrollListener实现下拉刷新和上拉加载更多功能

在refreshData()方法中,可以执行下拉刷新的操作,例如发送网络请求刷新数据,并更新RecyclerView的适配器。在onScrolled()方法中,通过判断滑动位置和总的item数量,来触发加载更多的操作。在实际开发中,你还可以根据项目的需求进行一些优化和扩展,例如添加加载失败的处理、优化加载更多的触发条件等。接下来,我们来实现下拉刷新的功能。在onLoadMore()方法中,可以执行上拉加载更多的操作,例如发送网络请求获取更多数据,并更新RecyclerView的适配器。

PixelInk的博客 364

Android 史上最简单的自定义listview上拉刷新下拉加载

今天有人问我,android上拉刷新下拉加载怎么实现。我对他说网上有很多例子啊,他说,网上是有很多例子,但是很杂,对于小白不友好。所以我就专门写了一个史上最简单的上拉刷新下拉加载。整个工程就一个activity,一个自定义listview,还有三个布局文件。是不是很简单很简洁? 老规矩先上程序运行截图 默认界面 下拉刷新 上拉加载 开始正题 首先我们先看三个布局...

迟到的微笑 7417

android 上拉加载,下拉刷新功能,Android RecyclerView 上拉加载更多及下拉刷新功能的实现方法...

RecyclerView 已经出来很久了,但是在项目中之前都使用的是ListView,最近新的项目上了都大量的使用了RecycleView.尤其是瀑布流的下拉刷新,网上吧啦吧啦没有合适的自己总结了一哈。先贴图上来看看:使用RecyclerView实现上拉加载更多和下拉刷新的功能我自己有两种方式:1.使用系统自带的Android.support.v4.widget.SwipeRefreshLayou...

weixin_31681589的博客 713

实现Android ListView带阻尼效果的下拉上拉刷新功能

ListView作为Android开发中极为重要的组件,其刷新机制对提升用户体验至关重要。本章将对ListView的刷新机制进行深入介绍,从基础的刷新流程开始,逐步分析其背后的原理和常见的优化手段。自定义Header/Footer是提升用户界面个性化和用户体验的重要手段。在ListView或RecyclerView中,Header和Footer区域提供了展示额外信息的空间,比如广告、重要通知、引导页等。通过自定义实现,开发者可以将更多的业务逻辑与界面风格融入应用,以满足产品的特定需求。

weixin_42575109的博客 961

Android MVP设计框架模板 之 漂亮ListView上拉刷新下拉加载更多

Android MVP设计框架模板 之 漂亮ListView上拉刷新下拉加载更多

xufeifandj的专栏 4597

Android实战:QQ风格滑动删除+上拉刷新+下拉加载完整实现

实现滑动删除不仅依赖事件处理,还需合理的组件架构支撑。选择合适的基类、科学组织 itemView 布局、维护内部状态,是打造稳定高性能控件的关键步骤。虽然提供了基础功能,但许多产品追求个性化UI,如QQ的萌宠动画、微博的星座主题等。这就要求我们能完全替换默认刷新头,实现高度自由的视觉表达。通过定义统一的接口协议,隔离UI层与业务逻辑层:// 触发刷新请求// 请求加载下一页// 删除指定条目// 状态变更通知。

weixin_42499004的博客 773

Android高级ListView组件实现与应用

本文还有配套的精品资源,点击获取 简介:在Android开发中,ListView组件是展示大量数据的常用方式。但是,为了更好地满足用户的交互需求,开发者需要扩展ListView功能,如实现上拉刷新、下拉加载更多、滑动删除、长按编辑和点击监听等。 XListView-Android-master 是一个开源项目,它集成了这些高级功能,使得开发者能够更方便地在应用...

weixin_35045970的博客 1624

Android自定义控件二

ListView, 是在原有的ListView的基础上, 增加下拉刷新头, 和加载更多的尾.

wchg21131的博客 118

Android高效实现ListView上拉下拉刷新与加载更多

本文还有配套的精品资源,点击获取 简介:在Android开发中,ListView组件常用于展示数据列表。为了改善用户体验,开发者需要为ListView添加上拉下拉刷新和加载更多数据的功能。本课程将探讨实现这些功能的不同方法,包括使用第三方库如SwipeRefreshLayout或XListView,以及自定义实现滚动监听逻辑。课程将涉及自定义行为、性能优化和不同设备及And...

weixin_42298778的博客 1511

Android-----XlistView上拉刷新下拉加载更多

1.想要实现xlistview上拉刷新下拉加载更多必须导入xlistview文件,在file-new-ImportModule-选择路径导入-导入完成之后在xlistview下的build.gradle更改下buildToolsVersion为“25.0.1”,最下方的改为..appcompat-v7:25.0.1 2.需要同步xlistview 否则不能引用里面的方法,具体为点击file-

leehbhs的博客 783

告别手动拖拽!PyQt5/PyQt6 QTableView列宽自适应窗口的三种实战方案(附性能对比)

本文详细介绍了PyQt5/PyQt6中QTableView列宽自适应窗口的三种实战方案,包括Stretch模式、resizeEvent手动计算和ResizeToContents模式,并附有性能对比。帮助开发者解决窗口大小变化时列宽不变的常见问题,提升用户体验和开发效率。

weixin_42538752的博客 233

Android布局基础与UI设计入门:打造精美应用界面

恭喜你!现在你已经掌握了Android UI开发的基础知识,包括各种布局类型、常用控件的使用以及一些UI优化技巧。这些知识将帮助你设计出既美观又实用的Android应用界面。

m0_65382359的博客 2056

超简单的 listview的第三方控件PullToRefreshListView 上拉刷新,下拉加载更多,分页加载

超简单的 listview的第三方控件PullToRefreshListView 上拉刷新,下拉加载更多,分页加载

随意而然的专栏 5578

AD9361 官方例程

文章目录一、官方资料1.1 介绍1.2 下载 和 使用1.3 相关资料二、步骤2.1 在Vivado中构建HDL2.2 在SDK中构建No- OS总结 一、官方资料 1.1 介绍 HDL参考设计介绍:AD9361 HDL Reference Designs 无操作系统介绍:AD9361 No-OS Software 1.2 下载 和 使用 HDL下载:Download HDL 在Vivado中构建HDL:Building HDL No-OS下载:Download No-OS 1.3 相关资料 AD

weixin_44630490的博客 3万+

Android RefreshListView上拉刷新下拉加载

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34161083的博客 138

Google AdSense收款前必做:W-8BEN税务认证的3个关键选择与1个提交时机小技巧

本文深入解析Google AdSense收款前必须完成的W-8BEN税务认证,重点介绍账号类型选择、特殊税率勾选、美国活动界定等3个关键决策,并分享提交时机的小技巧,帮助站长高效通过审核,确保广告收入顺利到账。

weixin_30443731的博客 671

audio语音相关的基础知识-VAD,ASR,AEC,AGC,BF等

有音素之间的间隙,也有静音和语音本身的间隙,为了对数据从时间上进行相对的校准,语音端点检测技术就应运而生了,因此端点检测技术可以决定这种校准的相对精度,使得同一内容的特征更趋于相同,当然,一般情况下是不可能完全相同的。盲源分离的目的是求得源信号的最佳估计,盲辨识的目的是求得传输通道的。这个技术的主要任务是从带有噪声的语音中准确的定位出语音的开始和结束点,因为语音中含有很长的静音,也就是把静音和实际语音分离开来,因为是语音数据的原始处理,所以VAD是语音信号处理过程的关键技术之一。呈现出与会者清晰的声音。

u013250861的博客 1034

自定义ListView的上拉刷新和下拉加载

今天自己写了一个 自定义ListView实现下拉加载和上拉刷新,别的不说了直接上代码 package com.wd.tech.mview; import android.annotation.SuppressLint; import android.content.Context; import android.os.Handler; import android.os.Message; i...

weixin_41649874的博客 426

【漏洞复现】用友U9 UMWebService.asmx 任意文件读取漏洞

用友U9存在任意文件读取漏洞,未授权的攻击者可以通过该漏洞读取服务器任意文件,造成敏感信息泄露。

qq_67810151的博客 620

Android——ListView上拉刷新与下拉刷新

本文记述ListView的上拉与下拉的刷新数据 首先需要自定义一个MyListView,继承于ListView public class MyListView extends ListView implements AbsListView.OnScrollListener { private View bottomview; //尾文件 private View headview; //头文件 private int totaItemCounts;//用于表示是下拉还是上拉

weixin_44288786的博客 895

TC3XX - MCAL知识点(三十八):AURIX™ TC3xx 的EDSADC实现旋变解码功能(理论知识+手册阅读)

本文系统梳理了英飞凌AURIX系列芯片EDSADC模块的技术手册内容。首先概述了该模块的架构特点,包括模拟/数字输入通道和总线地址信息。重点解析了手册中的关键知识点:1)模拟量输入的差分/单端模式;2)数字量连接的触发、数据源和时钟源选择;3)功能配置中的寄存器操作注意事项;4)输入通道的三种数据来源模式;5)滤波器链的组成和工作原理;6)旋变应用中的载波信号生成和延迟补偿机制。最后对SRM、SAUL、SBLL等专业术语进行了详细解释。全文为理解EDSADC模块的硬件特性和配置方法提供了系统的技术参考。

梦想技术家 594

【从0到1实战】C# WinForm开发工业级PLC监控系统(完整串口+Modbus RTU/TCP+数据库存储+源码+避坑指南)

做C#工控上位机开发的工程师,尤其是刚入行的新人,最核心的刚需就是开发一套完整的PLC监控系统。这也是工控领域最基础、最核心、应用最广的项目类型——小到单台PLC的设备测控,大到整线的产线监控,本质都是「上位机与PLC通讯,实现数据采集、参数下发、状态监控、数据存储」。碎片化的知识点学了很多,串口会用了、Modbus协议看了、数据库也懂点,但是始终无法把这些知识点串联起来,做出一套能落地的工业级PLC监控系统。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1560
上一篇: Android 网络编程 - OkHttp源码解析
下一篇: Android 三方登录 - QQ登录
JohanMan
博客等级 码龄10年 72粉丝 131原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值