zoukankan      html  css  js  c++  java
  • 【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));
            }
        }
    }

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

  • 相关阅读:
    thread同步测试
    thread互斥测试
    实验二测试
    第六章学习笔记
    opensslAPI
    第五章学习笔记
    stat命令的实现-mysate(必做)
    团队作业(三)
    第四章学习笔记
    学习笔记9
  • 原文地址:https://www.cnblogs.com/itgungnir/p/6735343.html
Copyright © 2011-2022 走看看