码迷,mamicode.com
首页 > 其他好文 > 详细

自定义控件学习——下拉刷新ListView

时间:2016-08-07 09:41:35      阅读:141      评论:0      收藏:0      [点我收藏+]

标签:

效果

 

技术分享

 

开始用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具体执行什么

自定义控件学习——下拉刷新ListView

标签:

原文地址:http://www.cnblogs.com/xurui1995/p/5745560.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!