zoukankan      html  css  js  c++  java
  • 自定义控件学习——下拉刷新ListView

    效果

    开始用Android Studio写了,还有挺多不明白这IDE用法的地方。。。。蛋疼

    主要思路

      1. 添加了自定义的头布局
        2. 默认让头布局隐藏setPadding.设置 -自身的高度
        3. ListView下拉的时候, 修改paddingTop, 让头布局显示出来
        4. 触摸动态修改头布局, 根据paddingTop.  
            - paddingTop = 0 完全显示
            - paddingTop < 不完全显示 -64(自身高度)完全隐藏
            - paddingTop > 0 顶部空白

        5. 松手之后根据当前的paddingTop决定是否执行刷新
            - paddingTop < 0 不完全显示, 恢复
            - paddingTop >= 0 完全显示, 执行正在刷新...

    其实就是通过设置padding控制头布局和脚步局的位置

    一:先写头布局和脚步局

    layout_header

    <?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:orientation="horizontal">
    
        <FrameLayout
            android:layout_margin="5dp"
            android:layout_width="50dp"
            android:layout_height="50dp" >
    
            <ImageView
                android:id="@+id/iv_arrow"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:src="@drawable/common_listview_headview_red_arrow" />
    
            <ProgressBar
                android:id="@+id/pb"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:indeterminateDrawable="@drawable/shape_progress"
                android:visibility="invisible" />
        </FrameLayout>
    
        <LinearLayout
            android:layout_margin="5dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:orientation="vertical" >
    
            <TextView
                android:id="@+id/tv_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                android:text="下拉刷新"
                android:textColor="#F00"
                android:textSize="18sp" />
    
            <TextView
                android:id="@+id/tv_desc_last_refresh"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                android:singleLine="true"
                android:text="最后刷新时间: 2015-10-11 09:20:35"
                android:textColor="#666"
                android:textSize="14sp" />
        </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:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="-360" >
    
        <!-- android:innerRadius="20dp" -->
        <!-- android:thickness="5dp" -->
        <shape
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:innerRadiusRatio="2.5"
            android:shape="ring"
            android:thicknessRatio="10"
            android:useLevel="false" >
            <gradient
                android:centerColor="#44FF0000"
                android:endColor="#00000000"
                android:startColor="#FF0000"
                android:type="sweep" />
        </shape>
    
    </rotate>

    layout_footer

    <?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"
        android:gravity="center"
        android:orientation="horizontal" >
    
        <ProgressBar
            android:layout_margin="5dp"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:indeterminateDrawable="@drawable/shape_progress" />
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="加载更多..."
            android:textColor="#F00"
            android:layout_marginLeft="15dp"
            android:textSize="18sp" />
    
    </LinearLayout>

    二。接下来是自定义的RefreshListView

    import java.text.SimpleDateFormat;
    
    import android.content.Context;
    import android.util.AttributeSet;
    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.AbsListView.OnScrollListener;
    import android.widget.ListView;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    
    import com.itheima74.refreshlist.R;
    
    /**
     * 包含下拉刷新功能的ListView
     * @author 
     *
     */
    public class RefreshListView extends ListView implements OnScrollListener{
    
        private View mHeaderView; // 头布局
        private float downY; // 按下的y坐标
        private float moveY; // 移动后的y坐标
        private int mHeaderViewHeight; // 头布局高度
        public static final int PULL_TO_REFRESH = 0;// 下拉刷新
        public static final int RELEASE_REFRESH = 1;// 释放刷新
        public static final int REFRESHING = 2; // 刷新中
        private int currentState = PULL_TO_REFRESH; // 当前刷新模式
        private RotateAnimation rotateUpAnim; // 箭头向上动画
        private RotateAnimation rotateDownAnim; // 箭头向下动画
        private View mArrowView;        // 箭头布局
        private TextView mTitleText;    // 头布局标题
        private ProgressBar pb;            // 进度指示器
        private TextView mLastRefreshTime; // 最后刷新时间
        private OnRefreshListener mListener; // 刷新监听
        private View mFooterView;        // 脚布局
        private int mFooterViewHeight;    // 脚布局高度
        private boolean isLoadingMore; // 是否正在加载更多
    
        public RefreshListView(Context context) {
            super(context);
            init();
        }
    
        public RefreshListView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public RefreshListView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init();
        }
    
        /**
         * 初始化头布局, 脚布局
         * 滚动监听
         */
        private void init() {
            initHeaderView();
            initAnimation();
            
            initFooterView();
            
            setOnScrollListener(this);
        }
    
        /**
         * 初始化脚布局
         */
        private void initFooterView() {
            mFooterView = View.inflate(getContext(), R.layout.layout_footer_list, null);
            
            mFooterView.measure(0, 0);
            mFooterViewHeight = mFooterView.getMeasuredHeight();
            
            // 隐藏脚布局
            mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
            
            addFooterView(mFooterView);
        }
    
        /**
         * 初始化头布局的动画
         */
        private void initAnimation() {
            // 向上转, 围绕着自己的中心, 逆时针旋转0 -> -180.
            rotateUpAnim = new RotateAnimation(0f, -180f, 
                    Animation.RELATIVE_TO_SELF, 0.5f, 
                    Animation.RELATIVE_TO_SELF, 0.5f);
            rotateUpAnim.setDuration(300);
            rotateUpAnim.setFillAfter(true); // 动画停留在结束位置
    
            // 向下转, 围绕着自己的中心, 逆时针旋转 -180 -> -360
            rotateDownAnim = new RotateAnimation(-180f, -360,
                    Animation.RELATIVE_TO_SELF, 0.5f, 
                    Animation.RELATIVE_TO_SELF, 0.5f);
            rotateDownAnim.setDuration(300);
            rotateDownAnim.setFillAfter(true); // 动画停留在结束位置
            
        }
    
        /**
         * 初始化头布局
         */
        private void initHeaderView() {
    
            mHeaderView = View.inflate(getContext(), R.layout.layout_header_list, null);
            mArrowView = mHeaderView.findViewById(R.id.iv_arrow);
            pb = (ProgressBar) mHeaderView.findViewById(R.id.pb);
            mTitleText = (TextView) mHeaderView.findViewById(R.id.tv_title);
            mLastRefreshTime = (TextView) mHeaderView.findViewById(R.id.tv_desc_last_refresh);
            
            
            // 提前手动测量宽高
            mHeaderView.measure(0, 0);// 按照设置的规则测量
            
            mHeaderViewHeight = mHeaderView.getMeasuredHeight();
            System.out.println(" measuredHeight: " + mHeaderViewHeight);
            
            // 设置内边距, 可以隐藏当前控件 , -自身高度
            mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
            
            // 在设置数据适配器之前执行添加 头布局/脚布局 的方法.
            addHeaderView(mHeaderView);
        }
        
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            
            // 判断滑动距离, 给Header设置paddingTop
            switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downY = ev.getY();
                System.out.println("downY: " + downY);
                
                break;
            case MotionEvent.ACTION_MOVE:
                moveY = ev.getY();
                System.out.println("moveY: " + moveY);
                // 如果是正在刷新中, 就执行父类的处理
                if(currentState == REFRESHING){
                    return super.onTouchEvent(ev);
                }
                
                
                float offset = moveY - downY; // 移动的偏移量
                // 只有 偏移量>0, 并且当前第一个可见条目索引是0, 才放大头部
                if(offset > 0 && getFirstVisiblePosition() == 0){
                    
    //            int paddingTop = -自身高度 + 偏移量
                    
                    int paddingTop = (int) (- mHeaderViewHeight + offset);
                    mHeaderView.setPadding(0, paddingTop, 0, 0);
                    
                    if(paddingTop >= 0 && currentState != RELEASE_REFRESH){// 头布局完全显示
                        System.out.println("切换成释放刷新模式: " + paddingTop);
                        // 切换成释放刷新模式
                        currentState = RELEASE_REFRESH;
                        updateHeader(); // 根据最新的状态值更新头布局内容
                    }else if(paddingTop < 0 && currentState != PULL_TO_REFRESH){ // 头布局不完全显示
                        System.out.println("切换成下拉刷新模式: " + paddingTop);
                        // 切换成下拉刷新模式
                        currentState = PULL_TO_REFRESH;
                        updateHeader(); // 根据最新的状态值更新头布局内容
                    }
                    
                    return true; // 当前事件被我们处理并消费
                }
                
                break;
            case MotionEvent.ACTION_UP:
                
                // 根据刚刚设置状态
                if(currentState == PULL_TO_REFRESH){
    //            - paddingTop < 0 不完全显示, 恢复
                    mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
                }else if(currentState == RELEASE_REFRESH){
    //            - paddingTop >= 0 完全显示, 执行正在刷新...
                    mHeaderView.setPadding(0, 0, 0, 0);
                    currentState = REFRESHING; 
                    updateHeader();
                }
                break;
    
            default:
                break;
            }
            
            return super.onTouchEvent(ev);
        }
    
        /**
         * 根据状态更新头布局内容
         */
        private void updateHeader() {
            switch (currentState) {
            case PULL_TO_REFRESH: // 切换回下拉刷新
                // 做动画, 改标题
                mArrowView.startAnimation(rotateDownAnim);
                mTitleText.setText("下拉刷新");
                
                break;
            case RELEASE_REFRESH: // 切换成释放刷新
                // 做动画, 改标题
                mArrowView.startAnimation(rotateUpAnim);
                mTitleText.setText("释放刷新");
                
                break;
            case REFRESHING: // 刷新中...
                mArrowView.clearAnimation();
                mArrowView.setVisibility(View.INVISIBLE);
                pb.setVisibility(View.VISIBLE);
                mTitleText.setText("正在刷新中...");
                
                if(mListener != null){
                    mListener.onRefresh(); // 通知调用者, 让其到网络加载更多数据.
                }
                
                break;
    
            default:
                break;
            }
        }
        
        /**
         * 刷新结束, 恢复界面效果
         */
        public void onRefreshComplete() {
            if(isLoadingMore){
                // 加载更多
                mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
                isLoadingMore = false;
            }else {
                // 下拉刷新
                currentState = PULL_TO_REFRESH;
                mTitleText.setText("下拉刷新"); // 切换文本
                mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);// 隐藏头布局
                pb.setVisibility(View.INVISIBLE);
                mArrowView.setVisibility(View.VISIBLE);
                String time = getTime();
                mLastRefreshTime.setText("最后刷新时间: " + time);
            }
            
        }
    
        private String getTime() {
            long currentTimeMillis = System.currentTimeMillis();
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return format.format(currentTimeMillis);
        }
    
        public interface OnRefreshListener{
            
            void onRefresh(); // 下拉刷新
            
            void onLoadMore();// 加载更多
        }
        
        public void setRefreshListener(OnRefreshListener mListener) {
            this.mListener = mListener;
        }
    
    //    public static int SCROLL_STATE_IDLE = 0; // 空闲
    //    public static int SCROLL_STATE_TOUCH_SCROLL = 1; // 触摸滑动
    //    public static int SCROLL_STATE_FLING = 2; // 滑翔
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // 状态更新的时候
            System.out.println("scrollState: " + scrollState);
            if(isLoadingMore){
                return; // 已经在加载更多.返回
            }
            
            // 最新状态是空闲状态, 并且当前界面显示了所有数据的最后一条. 加载更多
            if(scrollState == SCROLL_STATE_IDLE && getLastVisiblePosition() >= (getCount() - 1)){
                isLoadingMore = true;
                System.out.println("scrollState: 开始加载更多");
                mFooterView.setPadding(0, 0, 0, 0);
                
                setSelection(getCount()); // 跳转到最后一条, 使其显示出加载更多.
    
                if(mListener != null){
                    mListener.onLoadMore();
                }
            }
        }
    
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
            // 滑动过程
        }
    
    
        
        
    
    }

    思路是,先设置padding让头隐藏,然后根据 onTouchEvent的动作,判断出头布局应该处于的状态。脚步局也是如此。

    这里,定义了接口

    public interface OnRefreshListener{

    void onRefresh(); // 下拉刷新

    void onLoadMore();// 加载更多

    }

    这些方法是为了让调用者使用

    @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // 状态更新的时候
            System.out.println("scrollState: " + scrollState);
            if(isLoadingMore){
                return; // 已经在加载更多.返回
            }
            
            // 最新状态是空闲状态, 并且当前界面显示了所有数据的最后一条. 加载更多
            if(scrollState == SCROLL_STATE_IDLE && getLastVisiblePosition() >= (getCount() - 1)){
                isLoadingMore = true;
                System.out.println("scrollState: 开始加载更多");
                mFooterView.setPadding(0, 0, 0, 0);
                
                setSelection(getCount()); // 跳转到最后一条, 使其显示出加载更多.
    
                if(mListener != null){
                    mListener.onLoadMore();
                }
            }
        }


    这里我们看到有个 if(mListener != null){ mListener.onLoadMore(); }
    但是onLoadMore()方法到底在复写的呢,我们可以看下mainactivity

    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.Window;
    import android.widget.BaseAdapter;
    import android.widget.TextView;
    
    import java.util.ArrayList;
    
    public class MainActivity extends AppCompatActivity {
        private RefreshListView listview;
        private ArrayList<String> listDatas;
        private MyAdapter adapter;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
           
            setContentView(R.layout.activity_main);
            listDatas = new ArrayList<String>();
            for (int i = 0; i < 30; i++) {
                listDatas.add("这是一条ListView数据: " + i);
            }
            listview = (RefreshListView) findViewById(R.id.listview);
            listview.setRefreshListener(new RefreshListView.OnRefreshListener() {
    
                @Override
                public void onRefresh() {
                    new Thread(){
                        public void run() {
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
    
                            listDatas.add(0,"我是下拉刷新出来的数据!");
    
                            runOnUiThread(new Runnable() {
    
                                @Override
                                public void run() {
                                    adapter.notifyDataSetChanged();
                                    listview.onRefreshComplete();
                                }
                            });
                        };
    
                    }.start();
                }
    
                @Override
                public void onLoadMore() {
                    new Thread(){
                        public void run() {
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
    
                            listDatas.add("我是加载更多出来的数据!1");
                            listDatas.add("我是加载更多出来的数据!2");
                            listDatas.add("我是加载更多出来的数据!3");
    
                            runOnUiThread(new Runnable() {
    
                                @Override
                                public void run() {
                                    adapter.notifyDataSetChanged();
                                    listview.onRefreshComplete();
                                }
                            });
                        };
    
                    }.start();
                }
    
            });
    
    
            adapter = new MyAdapter();
            listview.setAdapter(adapter);
    
            }
    
    
    
    
    
    
    
    
    
    
    
    
        class MyAdapter extends BaseAdapter {
    
            @Override
            public int getCount() {
                return listDatas.size();
            }
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                TextView textView = new TextView(parent.getContext());
                textView.setTextSize(18f);
                textView.setText(listDatas.get(position));
    
                return textView;
            }
    
            @Override
            public Object getItem(int position) {
                return listDatas.get(position);
            }
    
            @Override
            public long getItemId(int position) {
                return position;
            }
    
        }
    }

     在这里,通过自定义Listview的setRefreshListener的方法,传入一个匿名类进行复写了接口要实现的方法。

    这是一种回调机智。

    简单的说像是,listView内部给定了一个接口,像是规定了一个规范,然后使用listview的类,如果想要实现某种功能,必须实现这个规范中的方法。

    像是我们考试,老师给试卷上印好了姓名和学号填空栏目,我们使用者每个人在栏目里写上自己的姓名和学号,再交试卷

    比如,我们button的点击事件

    btn1.setOnClickListener(new OnClickListener() {
     
          @Override
          public void onClick(View v) {
            // TODO Auto-generated method stub
            Toast tst = Toast.makeText(TestButtonActivity.this, "111111111", Toast.LENGTH_SHORT);
            tst.show();
     
          }
        });

    老师=谷歌开发人员,写button类的

    试卷=button

    姓名学号栏目=接口OnClickListener中onclick方法

    我们姓名学号=我们自己复写的onclick具体执行什么

  • 相关阅读:
    《Linux C一站式学习》第七章 结构体
    《Linux C一站式学习》第三章 简单的函数
    《Windows程序设计》第一章 起步
    CSS在线课程学习笔记
    《Windows程序设计》第三章 窗口和消息
    window.open与window.showModalDialog中主页面与从页面间的通信(原创) 中庸
    php文件实现将大文件导入到mysql数据库中
    我为何爱读代码?你为何也应当爱?
    解决phpMyAdmin导入mysql数据库超过2M的问题
    对linux交换分区swap的一些认识总结
  • 原文地址:https://www.cnblogs.com/xurui1995/p/5745560.html
Copyright © 2011-2022 走看看