【Android

  首先来介绍一下这个自定义View:

  • (1)这个自定义View的名称叫做 RefreshableListView ,继承自ListView类;
  • (2)在这个自定义View中,用户可以设置是否支持下拉刷新或上拉加载,当然也可以设置为都支持或都不支持;
  • (3)在这个自定义View中设置了下拉刷新和上拉加载的回调方法,用户可以自己编写下拉刷新和上拉加载的业务代码。

  接下来简单介绍一下这个自定义View中用到的技术点:

  • (1)为ListView添加头部和底部布局,分别调用addHeaderView() 和addFooterView() 方法;
  • (2)通过ListView的setPadding() 方法设置隐藏ListView的头部和底部布局;
  • (3)通过ListView的 post() 方法将头部布局和底部布局的测量操作post到ListView加载完成后进行;
  • (4)实现 OnScrollListener 接口,在 onScroll() 和 onScrollStateChanged() 方法中判断当前位置是否可以进行上拉或下拉操作;
  • (5)重写 onTouchEvent() 方法,通过手势操作头部布局和底部布局进行下拉刷新和上拉加载操作;
  • (6)使用 ObjectAnimator 属性动画进行头部布局中箭头的反转操作;
  • (7)定义了回调接口,让用户自己编写下拉刷新和上拉加载的业务代码;
  • (8)设置了两个标志位 isRefreshEnabled 和 isLoadEnabled ,让用户自己控制是否可以下拉刷新或上拉加载。

  下面是这个自定义View—— RefreshableListView 中的代码:

import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

/**
 * 自定义的可下拉刷新或上拉加载的ListView
 */
public class RefreshableListView extends ListView implements AbsListView.OnScrollListener {
    private static final int STATE_NORMAL = 0x000; // 正常状态(没有显示头部布局)
    private static final int STATE_PULLING = 0x001; // 正在下拉或上拉,但没有达到刷新或加载的要求的状态
    private static final int STATE_PREPARED = 0x002; // 达到刷新或加载的要求,松开手指就可以刷新或加载的状态
    private static final int STATE_REFRESHING = 0x003; // 正在刷新或加载的状态

    private View headerView; // 顶部布局
    private ImageView arrow; // 顶部布局中的箭头
    private ProgressBar headerProgress; // 顶部布局中的进度条
    private TextView headerTip; // 顶部布局中的提示信息
    private int headerHeight; // 头部布局的高度
    private boolean isRefreshEnabled; // 是否允许下拉刷新
    private boolean isRefreshable; // 是否可以下拉刷新

    private ProgressBar footerProgress; // 底部布局中的进度条
    private TextView footerTip; // 底部布局中的提示信息
    private int footerHeight; // 底部布局的高度
    private boolean isLoadEnabled; // 是否允许上拉加载
    private boolean isLoadable; // 是否可以上拉加载

    private int firstItemIndex; // 第一个可见Item的下标
    private int visibleItemCount; // 页面中可见的Item的个数
    private int totalItemCount; // ListView中加载的Item的总个数

    private int firstItemTopPadding; // 第一个Item的top值
    private int startY; // 记录手指按下时的Y坐标位置
    private int offsetY; // 记录手指拖动过程中Y坐标的偏移量
    private int rotateTime; // 旋转次数,用于控制箭头只旋转一次
    private boolean isScrollIdle; // 滑动动作是否是停止的

    private OnRefreshListener onRefreshListener;

    public RefreshableListView(Context context) {
        super(context);
        initView(context);
    }

    public RefreshableListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public RefreshableListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    /**
     * 初始化界面,添加顶部布局文件到ListView中
     */
    private void initView(Context context) {
        // 初始化头部布局及布局中的控件
        headerView = LayoutInflater.from(context).inflate(R.layout.sideworks_rlv_header, this, false);
        arrow = (ImageView) headerView.findViewById(R.id.rlv_header_iv_arrow);
        headerProgress = (ProgressBar) headerView.findViewById(R.id.rlv_header_progress_progressbar);
        headerTip = (TextView) headerView.findViewById(R.id.rlv_header_tv_tip);
        // 初始化底部布局及布局中的控件
        View footerView = LayoutInflater.from(context).inflate(R.layout.sideworks_rlv_footer, this, false);
        footerProgress = (ProgressBar) footerView.findViewById(R.id.rlv_footer_progress_progressbar);
        footerTip = (TextView) footerView.findViewById(R.id.rlv_footer_tv_tip);
        // 此时视图刚刚开始初始化,如果直接获取测量值会返回0,因此需要将这个操作post到初始化之后进行
        this.post(new Runnable() {
            @Override
            public void run() {
                headerHeight = headerView.getMeasuredHeight();
                // 布局文件中,头部布局100dp,底部布局60dp,因此偷个懒,用*0.6的方法得到底部布局的高度
                footerHeight = (int) (headerHeight * 0.6);
                setViewPadding(-headerHeight, -footerHeight);
            }
        });
        this.addHeaderView(headerView);
        this.addFooterView(footerView);
        this.setOnScrollListener(this);
    }

    /**
     * 设置RefreshableListView的上下边距(用于隐藏头部和底部布局)
     */
    private void setViewPadding(int topPadding, int bottomPadding) {
        this.setPadding(headerView.getPaddingLeft(), topPadding, headerView.getPaddingRight(), bottomPadding);
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        this.firstItemIndex = firstVisibleItem;
        this.visibleItemCount = visibleItemCount;
        this.totalItemCount = totalItemCount;
        View firstView = this.getChildAt(firstVisibleItem);
        if (firstView != null) {
            this.firstItemTopPadding = firstView.getTop();
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        isScrollIdle = scrollState == OnScrollListener.SCROLL_STATE_IDLE;
    }

    /**
     * 监听手指操作的事件(按下、滑动、抬起)
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            // 手指按下时,判断是否可以下拉刷新或上拉加载
            case MotionEvent.ACTION_DOWN:
                isRefreshable = false;
                if (isRefreshEnabled && firstItemIndex == 0 && firstItemTopPadding == -headerHeight) {
                    isRefreshable = true;
                } else if (isLoadEnabled && isScrollIdle && firstItemIndex + visibleItemCount == totalItemCount) {
                    isLoadable = true;
                }
                startY = (int) ev.getY();
                break;
            // 手指移动时,判断是否在下拉刷新或上拉加载,如果是,则动态改变头部布局或底部布局的状态
            case MotionEvent.ACTION_MOVE:
                offsetY = (int) ev.getY() - startY;
                if (isRefreshEnabled && isRefreshable && offsetY > 0) {
                    setViewPadding(-headerHeight + offsetY, -footerHeight);
                    if (offsetY >= headerHeight) {
                        setCurrentState(STATE_PREPARED);
                    } else {
                        setCurrentState(STATE_PULLING);
                    }
                } else if (isLoadEnabled && isLoadable && offsetY < 0) {
                    setViewPadding(-headerHeight, -footerHeight - offsetY);
                    if (offsetY <= -footerHeight) {
                        setCurrentState(STATE_PREPARED);
                    } else {
                        setCurrentState(STATE_PULLING);
                    }
                }
                break;
            // 手指抬起时,判断是否下拉或上拉到可以刷新或加载的程度,如果达到程度,则进行刷新或加载
            case MotionEvent.ACTION_UP:
                if (isRefreshEnabled && isRefreshable && offsetY > 0) {
                    if (offsetY <= headerHeight) {
                        setViewPadding(-headerHeight, -footerHeight);
                        setCurrentState(STATE_NORMAL);
                    } else {
                        setViewPadding(0, -footerHeight);
                        setCurrentState(STATE_REFRESHING);
                        onRefreshListener.onRefreshing(); // 调用接口的回调方法
                    }
                } else if (isLoadEnabled && isLoadable && offsetY < 0) {
                    if (offsetY >= -footerHeight) {
                        setViewPadding(-headerHeight, -footerHeight);
                        setCurrentState(STATE_NORMAL);
                    } else {
                        setViewPadding(-headerHeight, 0);
                        setCurrentState(STATE_REFRESHING);
                        onRefreshListener.onLoading(); // 调用接口的回调方法
                    }
                }
                isRefreshable = false;
                isLoadable = false;
                break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 根据当前的状态进行相应的处理
     */
    private void setCurrentState(int state) {
        switch (state) {
            // 普通状态:头部布局和尾部布局都隐藏,头部布局中显示箭头不显示进度条,底部布局中不显示进度条
            case STATE_NORMAL:
                headerProgress.setVisibility(View.GONE);
                arrow.setVisibility(View.VISIBLE);
                footerProgress.setVisibility(View.GONE);
                break;
            // 正在下拉后上拉,但没有达到刷新或加载的要求的状态:
            // 如果是下拉,则将头部布局中的箭头指向调整为指下,同时改变文本;
            // 如果是上拉,则改变文本
            case STATE_PULLING:
                if (isRefreshEnabled && isRefreshable) {
                    if (rotateTime == 1) {
                        ObjectAnimator toUpAnim = ObjectAnimator.ofFloat(arrow, "rotation", 180f, 0f);
                        toUpAnim.setDuration(200);
                        toUpAnim.start();
                        rotateTime--;
                    }
                    headerTip.setText("下拉可以刷新");
                } else if (isLoadEnabled && isLoadable) {
                    footerTip.setText("上拉加载更多");
                }
                break;
            // 下拉或上拉达到刷新或加载的条件,但还没有松手的状态:
            // 如果是下拉,则将头部布局中的箭头指向调整为指上,同时改变文本;
            // 如果是上拉,则改变文本
            case STATE_PREPARED:
                if (isRefreshEnabled && isRefreshable) {
                    if (rotateTime == 0) {
                        ObjectAnimator toUpAnim = ObjectAnimator.ofFloat(arrow, "rotation", 0f, 180f);
                        toUpAnim.setDuration(200);
                        toUpAnim.start();
                        rotateTime++;
                    }
                    headerTip.setText("松开手指刷新");
                } else if (isLoadEnabled && isLoadable) {
                    footerTip.setText("松开手指加载");
                }
                break;
            // 正在刷新或加载的状态:
            // 如果是下拉,则隐藏头部布局中的箭头,显示头部布局中的进度条,改变文本;
            // 如果是上拉,则显示底部布局中的进度条,改变文本
            case STATE_REFRESHING:
                if (isRefreshEnabled && isRefreshable) {
                    arrow.setVisibility(View.GONE);
                    headerProgress.setVisibility(View.VISIBLE);
                    if (rotateTime == 1) {
                        ObjectAnimator toUpAnim = ObjectAnimator.ofFloat(arrow, "rotation", 180f, 0f);
                        toUpAnim.setDuration(200);
                        toUpAnim.start();
                        rotateTime--;
                    }
                    headerTip.setText("正在刷新......");
                } else if (isLoadEnabled && isLoadable) {
                    footerProgress.setVisibility(View.VISIBLE);
                    footerTip.setText("正在加载......");
                }
                break;
        }
    }

    /**
     * 刷新结束后必须调用这个方法来重置一些参数
     */
    public void onRefreshComplete() {
        setViewPadding(-headerHeight, -footerHeight);
        setCurrentState(STATE_NORMAL);
    }

    /**
     * 设置是否允许下拉刷新和上拉加载
     */
    public void setEnables(boolean isRefreshEnabled, boolean isLoadEnabled) {
        this.isRefreshEnabled = isRefreshEnabled;
        this.isLoadEnabled = isLoadEnabled;
    }

    /**
     * 监听下拉刷新的接口
     */
    interface OnRefreshListener {
        void onRefreshing(); // 在下拉刷新的时候回调的方法

        void onLoading(); // 在上拉加载的时候回调的方法
    }

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

  头部布局文件 sideworks_rlv_header.xml 中的代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100.0dip"
    android:background="#DEDEDE"
    android:gravity="center"
    android:orientation="horizontal">

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/rlv_header_iv_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:contentDescription="@string/app_name"
            android:src="@mipmap/img_rlv_arrow" />

        <ProgressBar
            android:id="@+id/rlv_header_progress_progressbar"
            android:layout_width="35.0dip"
            android:layout_height="35.0dip"
            android:layout_centerInParent="true"
            android:visibility="gone" />
    </RelativeLayout>

    <TextView
        android:id="@+id/rlv_header_tv_tip"
        android:layout_width="wrap_content"
        android:layout_height="100.0dip"
        android:layout_marginLeft="10.0dip"
        android:gravity="center"
        android:text="下拉可以刷新"
        android:textColor="#444444"
        android:textSize="16.0sp"
        android:textStyle="bold" />

</LinearLayout>

  底部布局文件 sideworks_rlv_footer.xml 中的代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:id="@+id/rlv_footer_ly_content"
        android:layout_width="match_parent"
        android:layout_height="60.0dip"
        android:background="#DEDEDE"
        android:gravity="center"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/rlv_footer_progress_progressbar"
            android:layout_width="30.0dip"
            android:layout_height="30.0dip"
            android:visibility="gone" />

        <TextView
            android:id="@+id/rlv_footer_tv_tip"
            android:layout_width="wrap_content"
            android:layout_height="60.0dip"
            android:layout_marginLeft="10.0dip"
            android:gravity="center"
            android:text="上拉加载更多"
            android:textColor="#444444"
            android:textSize="16.0sp"
            android:textStyle="bold" />
    </LinearLayout>

</LinearLayout>

  控件中每一条数据的布局文件 listitem_rlv_listview.xml 中的代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="90.0dip"
    android:background="@android:color/white">

    <ImageView
        android:id="@+id/rlv_listitem_iv_image"
        android:layout_width="65.0dip"
        android:layout_height="65.0dip"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10.0dip"
        android:contentDescription="@string/app_name"
        android:src="@mipmap/img_rlv_listitem_image" />

    <TextView
        android:id="@+id/rlv_listitem_tv_name"
        android:layout_width="wrap_content"
        android:layout_height="90.0dip"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10.0dip"
        android:layout_toRightOf="@id/rlv_listitem_iv_image"
        android:gravity="center"
        android:textColor="@android:color/black"
        android:textSize="16.0sp" />

    <Button
        android:id="@+id/rlv_listitem_btn_download"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="10.0dip"
        android:text="Download" />

</RelativeLayout>

  适配器类文件 ListViewAdapter.java 中的代码:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

/**
 * ListView的数据适配器
 */
public class ListViewAdapter extends BaseAdapter {
    private Context context;
    private List<String> nameList;

    public ListViewAdapter(Context context, List<String> nameList) {
        this.context = context;
        this.nameList = nameList;
    }

    @Override
    public int getCount() {
        return nameList.size();
    }

    @Override
    public Object getItem(int position) {
        return nameList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = LayoutInflater.from(context).inflate(R.layout.listitem_rlv_listview, parent, false);
            holder = new ViewHolder();
            holder.image = (ImageView) convertView.findViewById(R.id.rlv_listitem_iv_image);
            holder.name = (TextView) convertView.findViewById(R.id.rlv_listitem_tv_name);
            holder.download = (Button) convertView.findViewById(R.id.rlv_listitem_btn_download);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.image.setImageResource(R.mipmap.ic_launcher);
        holder.name.setText(nameList.get(position));
        holder.download.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context, nameList.get(position) + " Clicked!", Toast.LENGTH_SHORT).show();
            }
        });
        return convertView;
    }

    private static class ViewHolder {
        ImageView image;
        TextView name;
        Button download;
    }
}

  主界面的JAVA文件 MainActivity.java 中的代码:

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private RefreshableListView listview; // 自定义ListView控件

    private List<String> nameList; // RefreshableListView控件中显示的数据的数据集
    private ListViewAdapter adapter; // RefreshableListView控件的适配器

    // Handler更新UI界面
    private Handler refreshHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                // 下拉刷新的回调,在列表的最前面插入一条数据
                case 0:
                    nameList.add(0, "新加入的名称!!");
                    break;
                // 上拉加载的回调,在列表的最后添加一条数据
                case 1:
                    nameList.add("新加入的名称!!");
                    break;
            }
            // ListView的数据适配器更新数据集
            adapter.notifyDataSetChanged();
            // 必须调用这个方法,重置头部布局或底部布局的视图
            listview.onRefreshComplete();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 通过ID找到我们的自定义控件RefreshableListView
        listview = (RefreshableListView) this.findViewById(R.id.rlv_lv_listview);
        // 初始化RefreshableListView控件中显示的数据集
        initNameList();
        // 创建RefreshableListView的数据适配器
        adapter = new ListViewAdapter(MainActivity.this, nameList);
        // 为RefreshableListView适配数据
        listview.setAdapter(adapter);
        // 设置是否可以下拉刷新或上拉加载,这里设置的是可以下拉刷新,不可以上拉加载
        listview.setEnables(true, false);
        // 设置RefreshableListView的回调
        listview.setOnRefreshListener(new RefreshableListView.OnRefreshListener() {
            // 下拉刷新的回调方法,在这个方法中停留2秒后发送一条消息给Handler
            @Override
            public void onRefreshing() {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        refreshHandler.sendEmptyMessage(0);
                    }
                }).start();
            }

            // 上拉加载的回调方法,在这个方法中停留2秒后发送一条消息给Handler
            @Override
            public void onLoading() {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        refreshHandler.sendEmptyMessage(1);
                    }
                }).start();
            }
        });
    }

    /**
     * 初始化RefreshableListView控件中显示的数据集
     */
    private void initNameList() {
        nameList = new ArrayList<>();
        for (int i = 0; i < 15; i++) {
            nameList.add("APP名称" + (i + 1));
        }
    }
}

  最后上一张效果图(这张效果图是同时支持下拉刷新和上拉加载功能的),如下:

原文地址:https://www.cnblogs.com/itgungnir/p/6735343.html