• 自定义ListView实现下拉刷新,下拉加载的功能


    package com.loaderman.myrefreshlistviewdemo;
    
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.animation.Animation;
    import android.view.animation.RotateAnimation;
    import android.widget.AbsListView;
    import android.widget.ImageView;
    import android.widget.ListView;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * 
     * 实现步骤:
     * 1、给ListView添加头布局
     * 2、默认让ListView的头布局隐藏起来
     * 负的paddingTop的值
     * 如何获取头布局的高度
     * 3、慢慢的将头布局拖出来
     * 获取在ListView中的滑动偏移量--onTouchEvent
     * 关于起点坐标的获取dispatchTouchEvent
     * 4、给RefreshListView定义了三种状态
     * refreshUi:根据当前的状态刷新控件的显示
     * 在状态发生改变的时候来调用此方法即可
     * 5、增加了动画效果
     * clearAnimation的使用
     * 6、处理up的事件
     * STATE_PULL_TO_REFRESH的时候up
     * 隐藏头布局
     * STATE_RELEASE_TO_REFRESH的时候up
     * 显示头布局
     * 更新状态--STATE_REFRESHING
     * 通知观察者去加载数据
     * 7、观察者设计模式的使用
     * 找出被观察者
     * 定义观察者接口,接口中的方法就是观察者感兴趣的事件
     * 在被观察中存储观察者的引用
     * 在事件发生的时候,通知观察者
     * 为什么要用接口而不使用抽象类--单继承,多实现
     * 8、由TabDetailPager来通知RefreshListView数据加载完成
     * setOnRefreshComplete
     * 更新状态,隐藏头布局
     * 9、设置时间的显示
     * 存在sp中
     * 10、自定义ProgressBar的效果
     * 上拉加载:
     * 1、添加脚布局,默认隐藏
     * 2、增加了滚动监听,
     * idle,显示最后一个条目的时候,显示脚布局
     * 3、通知观察者加载下一页的数据
     * 4、加载下一页数据的逻辑
     * 将下一页数据的集合添加到上一页数据的集合红,不能new Adapter
     * 5、TabDetailPager通知ListView下一页数据加载完成
     * 重置isLoadingMore
     * 隐藏脚布局
     */
    
    public class RefreshListView extends ListView {
    
        public static final int STATE_PULL_TO_REFRESH    = 0;
        public static final int STATE_RELEASE_TO_REFRESH = 1;
        public static final int STATE_REFRESHING         = 2;
    
        private int mCurrentState = STATE_PULL_TO_REFRESH;//定义ListView当前的状态
    
        private float           startY;
        private int             headerViewHeight;
        private View            headerView;
        private ImageView       ivArrow;
        private ProgressBar     pb;
        private TextView        tvTips;
        private TextView        tvDate;
        private RotateAnimation downAnimation;
        private RotateAnimation upAnimation;
        private View            footerView;
        private int             footerViewHeight;
    
        public RefreshListView(Context context) {
            this(context, null);
        }
    
        public RefreshListView(Context context, AttributeSet attrs) {
            this(context, attrs, -1);
        }
    
        public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initHeaderView();
            initAnimation();
            initFooterView();
        }
    
    
        private void initAnimation() {
            upAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            upAnimation.setFillAfter(true);
            upAnimation.setDuration(200);
            downAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            downAnimation.setFillAfter(true);
            downAnimation.setDuration(200);
    
        }
    
        private void initHeaderView() {
            //头布局越早添加,位于越上边
            headerView = View.inflate(getContext(), R.layout.layout_refresh_header, null);
            ivArrow = (ImageView) headerView.findViewById(R.id.ivArrow);
            pb = (ProgressBar) headerView.findViewById(R.id.pb);
            tvTips = (TextView) headerView.findViewById(R.id.tvTips);
            tvDate = (TextView) headerView.findViewById(R.id.tvDate);
    
            String lastUpdateTime = PrefUtils.getString(getContext(), "lastUpdateTime", "");
            tvDate.setText(lastUpdateTime);
            //设置一个控件的高度或者宽度的信息得找LayoutParams
            //如果设置一个负的padding的值,只能在代码中设置才会其效果
            //measure-layout-draw
            //千万不要在Activity的onCreate方法中获取一个控件的宽度或者高度或者位置信息
            //监听视图树
            /*headerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
    
                }
            });*/
            //手动测量
            headerView.measure(0, 0);//将测量的工作交给系统来完成,我们不参与任何的限制的意见
            //获取测量之后的宽度或者高度信息
            headerViewHeight = headerView.getMeasuredHeight();
            headerView.setPadding(0, -headerViewHeight, 0, 0);
            this.addHeaderView(headerView);
        }
    
        private boolean isLoadingMore = false;
    
        private void initFooterView() {
            footerView = View.inflate(getContext(), R.layout.layout_refresh_footer, null);
            footerView.measure(0, 0);
            footerViewHeight = footerView.getMeasuredHeight();
            footerView.setPadding(0, -footerViewHeight, 0, 0);
            this.addFooterView(footerView);
    
            //给ListView增加一个监听
            this.setOnScrollListener(new OnScrollListener() {
                @Override
                public void onScrollStateChanged(AbsListView view, int scrollState) {
                    int lastVisiblePosition = getLastVisiblePosition();
                    if (scrollState == SCROLL_STATE_IDLE && lastVisiblePosition == getCount() - 1 && !isLoadingMore) {
                        //System.out.println("到底了...");
                        Log.i("RefreshListView", "到底了...");
                        isLoadingMore = true;
                        //将脚布局显示出来
                        footerView.setPadding(0, 0, 0, 0);
                        //自动滑到脚布局的位置,让脚布局可以一下子就能够看得见
                        setSelection(getCount() - 1);
    
                        notifyLoadMore();//通知观察者去加载下一页的数据
    
                    }
                }
    
                @Override
                public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    
                }
            });
        }
    
        //一旦事件到达了一个控件上,一定,最先,会调用dispatchTouchEvent
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            int action = ev.getAction();
            if (action == MotionEvent.ACTION_DOWN) {
                startY = ev.getY();//在这个父控件得到事件的时候,就把起点坐标初始化,这样就不会受制于子控件是否消费了事件,起点坐标就会很精确了
            }
            return super.dispatchTouchEvent(ev);
        }
    
        //onTouchEvent的来源:
        //1、自身拦截   2、子控件回传
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            int action = ev.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    startY = ev.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
    
                    if (mCurrentState == STATE_REFRESHING) {
                        break;
                    }
    
                    float moveY = ev.getY();
                    float dy = moveY - startY;
                    //什么情况下需要把头布局拖出来
                    int firstVisiblePosition = getFirstVisiblePosition();
                    //1、下拉  2、显示的第0个条目是下拉刷新头布局
                    if (dy > 0 && firstVisiblePosition == 0) {
                        int paddingTop = (int) (dy - headerViewHeight);
                        headerView.setPadding(0, paddingTop, 0, 0);
    
                        int oldState = mCurrentState;
                        if (paddingTop < 0) {
                            //头布局有一部分没有显示出来
                            mCurrentState = STATE_PULL_TO_REFRESH;
                        } else {
                            mCurrentState = STATE_RELEASE_TO_REFRESH;
                        }
    
                        //在状态发生改变的时候才需要刷新UI
                        if (oldState != mCurrentState) {
                            refreshUi();
                        }
    
    
                        return true;//代表消费了事件
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    if (mCurrentState == STATE_PULL_TO_REFRESH) {
                        //将头布局隐藏起来
                        headerView.setPadding(0, -headerViewHeight, 0, 0);
                    } else if (mCurrentState == STATE_RELEASE_TO_REFRESH) {
                        //改变当前的状态,刷新控件
                        mCurrentState = STATE_REFRESHING;
                        refreshUi();
                        //将头布局完全显示出来
                        headerView.setPadding(0, 0, 0, 0);
                        //去重写加载网络上的数据
                        //tabDetailPager.getDataFromServer();
                        notifyRefresh();
                    }
                    break;
            }
            return super.onTouchEvent(ev);
        }
    
        public void setOnRefreshComplete(boolean success) {
            //1、更新当前的状态
            mCurrentState = STATE_PULL_TO_REFRESH;
            pb.setVisibility(View.INVISIBLE);
            ivArrow.setVisibility(View.VISIBLE);
            tvTips.setText("下拉刷新");
            //2、隐藏头布局
            headerView.setPadding(0, -headerViewHeight, 0, 0);
    
            if (success) {
                //更新tvDate的显示
                setCurrentDate();
            }
    
        }
    
        private void setCurrentDate() {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            String currentDate = sdf.format(new Date());
            tvDate.setText(currentDate);
    
            PrefUtils.setString(getContext(), "lastUpdateTime", currentDate);
        }
    
        public void setOnLoadMoreComplete() {
            isLoadingMore = false;
            //隐藏脚布局
            footerView.setPadding(0, -footerViewHeight, 0, 0);
    
        }
    
    
        //定义观察者接口
        public interface OnRefreshListener {
            public void onRefresh();
    
            public void onLoadMore();
        }
    
        //保存观察者的实例对象
        private OnRefreshListener listener;
    
        public void setOnRefreshListener(OnRefreshListener listener) {
            this.listener = listener;
        }
    
        //通知观察者
        private void notifyRefresh() {
            if (listener != null) {
                listener.onRefresh();
            }
        }
    
        private void notifyLoadMore() {
            if (listener != null) {
                listener.onLoadMore();
            }
        }
    
        /*private TabDetailPager tabDetailPager;
    
        public void setTabDetailPager(TabDetailPager tabDetailPager) {
            this.tabDetailPager = tabDetailPager;
        }*/
    
        private void refreshUi() {
            switch (mCurrentState) {
                case STATE_PULL_TO_REFRESH:
                    pb.setVisibility(View.INVISIBLE);//INVISIBLE会占位,GONE不会占位
                    ivArrow.setVisibility(View.VISIBLE);
                    tvTips.setText("下拉刷新");
                    ivArrow.startAnimation(downAnimation);
                    break;
                case STATE_RELEASE_TO_REFRESH:
                    pb.setVisibility(View.INVISIBLE);
                    ivArrow.setVisibility(View.VISIBLE);
                    ivArrow.startAnimation(upAnimation);
                    tvTips.setText("松开刷新");
                    break;
                case STATE_REFRESHING:
                    pb.setVisibility(View.VISIBLE);
                    ivArrow.clearAnimation();//要控制一个控件的可见度的时候,需要先移除之前设置过的动画
                    ivArrow.setVisibility(View.INVISIBLE);
                    tvTips.setText("正在刷新");
                    break;
            }
        }
    }
    

     layout_refresh_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="match_parent"
        android:gravity="center"
        android:orientation="horizontal"
        >
    
        <ProgressBar
            android:id="@+id/pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:indeterminateDrawable="@drawable/shape_progress"/>
    
        <TextView
            android:id="@+id/tvTips"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="正在加载"
            android:textColor="#F00"
            android:textSize="16sp"/>
    
    </LinearLayout>
    

     layout_refresh_header.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    
        <FrameLayout
            android:layout_margin="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
    
            <ImageView
                android:id="@+id/ivArrow"
                android:layout_gravity="center"
                android:layout_width="wrap_content"
                android:src="@drawable/common_listview_headview_red_arrow"
                android:layout_height="wrap_content"/>
    
            <ProgressBar
                android:id="@+id/pb"
                android:visibility="invisible"
                android:indeterminateDrawable="@drawable/shape_progress"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
    
        </FrameLayout>
    
        <LinearLayout
            android:layout_margin="5dp"
            android:gravity="center"
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:layout_height="wrap_content">
    
            <TextView
                android:layout_width="wrap_content"
                android:text="下拉刷新"
                android:id="@+id/tvTips"
                android:textColor="#F00"
                android:textSize="16sp"
                android:layout_height="wrap_content"/>
    
            <TextView
                android:layout_width="wrap_content"
                android:text="2016-12-17"
                android:id="@+id/tvDate"
                android:textColor="#ccc"
                android:textSize="12sp"
                android:layout_height="wrap_content"/>
    
        </LinearLayout>
    
    
    </LinearLayout>
    

     shape_progress.xml

    <?xml version="1.0" encoding="utf-8"?>
    <rotate xmlns:android="http://schemas.android.com/apk/res/android"
        android:fromDegrees="0"
            android:toDegrees="720"
            android:pivotY="50%"
            android:pivotX="50%"
        >
        <shape
               android:innerRadius="15dp"
               android:shape="ring"
               android:thickness="3dp"
               android:useLevel="false"
            >
            <!--<solid android:color="@android:"-->
            <gradient
                android:startColor="#f00"
                android:centerColor="#af00"
                android:endColor="#fff"
                />
        </shape>
    </rotate>
    
    package com.loaderman.myrefreshlistviewdemo;
    
    import android.content.Context;
    import android.content.SharedPreferences;
    
    /**
     * 关于SharedPreference的工具类
     */
    
    public class PrefUtils {
        public static String getString(Context context,String key,String defValue) {
            SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
            String retString = sp.getString(key, defValue);
            return retString;
        }
        public static void setString(Context context,String key ,String value) {
            SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
            SharedPreferences.Editor edit = sp.edit();
            edit.putString(key, value);
            edit.commit();
        }
    }
    

     代码使用:

    package com.loaderman.myrefreshlistviewdemo;
    
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.AdapterView;
    import android.widget.BaseAdapter;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import java.util.ArrayList;
    import java.util.Random;
    
    public class MainActivity extends AppCompatActivity implements RefreshListView.OnRefreshListener {
    
        private RefreshListView lvListNews;
        private ArrayList mList;
        private MyListAdapter myListAdapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mList = new ArrayList<>();
            for (int i = 0; i < 30; i++) {
                mList.add("我是天才" + i + "号");
            }
            lvListNews = (RefreshListView) findViewById(R.id.lvListNews);
            lvListNews.setOnRefreshListener(this);
            lvListNews.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    
                }
            });
            myListAdapter = new MyListAdapter();
            lvListNews.setAdapter(myListAdapter);
        }
        //下拉刷新
        @Override
        public void onRefresh() {
            final Random random = new Random();
            mList.add(0, "我是天才" + random.nextInt(100) + "号");
            Toast.makeText(MainActivity.this, "刷新了一条数据", Toast.LENGTH_SHORT).show();
            //刷新完成
            lvListNews.setOnRefreshComplete(true);
            myListAdapter.notifyDataSetChanged();
        }
        //上拉加载
        @Override
        public void onLoadMore() {
            // 添加数据
            for (int i = 30; i < 35; i++) {
                mList.add("我是天才" + i+ "号");
                // 这里要放在里面刷新,放在外面会导致刷新的进度条卡住
                myListAdapter.notifyDataSetChanged();
            }
             //加载完成
            lvListNews.setOnLoadMoreComplete();
            Toast.makeText(MainActivity.this, "加载了" + 5 + "条数据", Toast.LENGTH_SHORT).show();
        }
        class MyListAdapter extends BaseAdapter {
    
    
            @Override
            public int getCount() {
                return mList.size();
            }
    
            @Override
            public Object getItem(int position) {
                return mList.get(position);
            }
    
            @Override
            public long getItemId(int position) {
                return position;
            }
    
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                ViewHolder holder = null;
                if(convertView == null){
                    convertView = View.inflate(MainActivity.this, R.layout.item_news_tab_detail, null);
                    holder = new ViewHolder();
                    holder.tvContent = (TextView) convertView.findViewById(R.id.tvContent);
                    convertView.setTag(holder);
                } else {
                    holder = (ViewHolder) convertView.getTag();
                }
                holder.tvContent.setText(mList.get(position)+"");
                return convertView;
            }
        }
    
        static class ViewHolder {
            TextView tvContent;
        }
    
    }
    

     activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.loaderman.myrefreshlistviewdemo.MainActivity">
        <com.loaderman.myrefreshlistviewdemo.RefreshListView
            android:id="@+id/lvListNews"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
           />
    </RelativeLayout>
    

     效果图:

  • 相关阅读:
    apache2+django+virtualenv 服务器部署实战
    从 relu 的多种实现来看 torch.nn 与 torch.nn.functional 的区别与联系
    Causal Corpus 事件因果关系语料统计
    Event StoryLine Corpus 论文阅读
    哈工大计算机网络Week2-网络应用数据交换
    哈工大计算机网络Week3-传输层
    哈工大计算机网络Week1-网络应用
    哈工大计算机网络Week0-概述
    python爬虫入门
    对scanner.close方法的误解以及无法补救的错误
  • 原文地址:https://www.cnblogs.com/loaderman/p/6532804.html
Copyright © 2020-2023  润新知