自定义下拉刷新

效果图:

自定义下拉刷新,主要就是对头部的控制,大体可以分为3步:

1. 控件初加载时,把头部隐藏起来;

2. 给listview设置touch事件,在手指移动过程中计算差值,并赋值给头部,设置头部隐藏或者显示;

3. 头部完全显示之后,就是对头部里view的操作,例如旋转箭头,更改文字,还有显示更新时间。

代码里面个人觉得需要记录点的知识点:

1.

  关于LayoutInflater类inflate(intresource, ViewGroup root, boolean attachToRoot)方法三个参数的含义

    resource:需要加载布局文件的id,意思是需要将这个布局文件中加载到Activity中来操作。

    root:需要附加到resource资源文件的根控件,什么意思呢,
    就是inflate()会返回一个View对象,如果第三个参数attachToRoot为true,就将这个root作为根对象返回,否则仅仅将这个root对象的LayoutParams属性附加到resource对象的根布局对象上,也就是将布局文件resource的布局参数转换为外层root可以接受的类型,比如root是一个LinearLayout自己要转换的resource里面有layout_width=”fill_parent”,和layout_height=”fill_parent”参数,但是这些参数没有外部环境,它们对应的对象都是ViewGroup.LayoutParams对象,root参数让系统将ViewGroup.LayoutParams对象转换为LinearLayout.LayoutParams对象。

 attachToRoot:是否将root附加到布局文件的根视图上

关于inflate参数通俗点说,最关键的就是root,当root设置不为空且attachToRoot=true,root会作为父布局存在,resource会变为子布局,当设置不为空且attachToRoot=false,resource布局会引用root布局的参数LayoutParam,当root=null ,则直接返回布局

2. MarginLayoutParams:

ViewGroup.MarginLayoutParams是ViewGroup.LayoutParams的一个继承者,主要用于定义和边缘的空白,它具有android:layout_marginTop,android:layout_marginLeft,android:layout_marginBottom,android:layout_marginRight几个XML属性,表示四个方向的边缘空白

3. ViewConfiguration:

ViewConfiguration.get(getContext()).getScaledTouchSlop():最小滑动距离;

ViewConfiguration这个类主要定义了UI中所使用到的标准常量,像超时、尺寸、距离,如果我们需要得到这些常量的数据,我们就可以通过这个类来获取,具体方法如下:
1、获取ViewConfiguration对象,由于ViewConfiguration的构造方法为私有的,只能通过这个静态方法来获取到该对象。

ViewConfiguration configure = ViewConfiguration.get(context);

更多ViewConfiguration 参数详情

4.

listView.getFirstVisiblePosition():获取可视区域第一个可见的item的id
listView.getLastVisiblePosition();获取可视区域最后一个可见的item的id

自定义下拉刷新代码如下:

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.view.View.OnTouchListener;

/**
 * 下拉刷新
 */
public class MPullRefreshView extends LinearLayout implements OnTouchListener{
    /** 下拉状态*/
    public static final int PULL_STATUS=0;
    /** 释放立即刷新状态*/
    public static final int RELEASE_TO_REFRESH_STATUS=1; 
    /** 正在刷新状态*/
    public static final int REFRESHING_STATUS=2; 
    /** 刷新完成或者未完成状态*/
    public static final int REFRESH_FINISH_STATUS=3; 
    /** 刷新状态*/
    private int refreshStatus=REFRESH_FINISH_STATUS;
    /** 记录上一次的状态是什么,避免进行重复操作*/
    private int lastStatus = refreshStatus;
    /** 头部回滚速度*/
    private int HEADER_SCROLL_BACK=-20;
    /** 第一次加载*/
    private boolean loadone;
    /** 是否允许下拉*/
    private boolean allowpull;
    /** 内容*/
    private ListView listView;
    /** 最大下拉距离*/
    private int mTouchSlop;
    /** 手指按下时的屏幕纵坐标*/
    private float Ydown;
    /** 下拉刷新头部*/
    private View header;
    /** 下拉刷新头部箭头*/
    private ImageView arrow;
    /** 下拉刷新头部加载*/
    private ProgressBar progressBar;
    /** 下拉刷新头部文字提示*/
    private TextView description;
    /** 下拉刷新头部隐藏高度*/
    private int hideheaderheight;
    /** 下拉刷新头部距离边缘的相关参数*/
    private MarginLayoutParams headermarginLayoutParams;
    /** 下拉刷新回调接口*/
    private PullToRefreshListener listener;

    public MPullRefreshView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public MPullRefreshView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MPullRefreshView(Context context) {
        super(context);
        init();
    }
    /**
     * 添加刷新接口
     * @param listener
     */
    public void setonRefreshListener(PullToRefreshListener listener){
        this.listener=listener;
    }
    /**
     * 结束刷新状态
     */
    public void finishRefreshing(){
        new HideHeader().execute();
    }
    private void init() {
        header=LayoutInflater.from(getContext()).inflate(R.layout.mpull_refresh_view_header,this,false);
        arrow=(ImageView) header.findViewById(R.id.arrow);
        progressBar=(ProgressBar) header.findViewById(R.id.progress_bar);
        description=(TextView) header.findViewById(R.id.description);
        mTouchSlop=ViewConfiguration.get(getContext()).getScaledTouchSlop();
        setOrientation(VERTICAL);
        addView(header,0);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //当布局改变 或者是第一次加载 隐藏头部
        if(changed || !loadone){
            hideheaderheight=-header.getHeight();
            headermarginLayoutParams=(MarginLayoutParams) header.getLayoutParams();
            //将距离上部边缘距离设置为高度的负值 ,即头部隐藏
            headermarginLayoutParams.topMargin=hideheaderheight;
            listView=(ListView) getChildAt(1);
            listView.setOnTouchListener(this);
            loadone=true;
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //允许listview可下拉前先判断listview是否到达顶部
        JudgeallowPull();
        if(allowpull){
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Ydown= event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                float Ymove=event.getRawY();
                int distance=(int) (Ymove-Ydown);
                //上滑 并且头部是隐藏状态 则屏蔽该事件
                if(distance<0 && headermarginLayoutParams.topMargin<=hideheaderheight){
                    return false;
                }
                //滑动距离小于感应距离,屏蔽事件
                if(distance<mTouchSlop){
                    return false;
                }
                //不是正在刷新状态,表示头部没有完全显示完
                if(refreshStatus!=REFRESHING_STATUS){
                    if(headermarginLayoutParams.topMargin>0){
                        refreshStatus=RELEASE_TO_REFRESH_STATUS;//请求释放
                    }else{
                        refreshStatus=PULL_STATUS;//下拉状态
                    }
                    headermarginLayoutParams.topMargin=(distance/2)+hideheaderheight;
                    header.setLayoutParams(headermarginLayoutParams);
                }
                break;
            case MotionEvent.ACTION_UP:
            default:
                //判断当前头部状态
                if(refreshStatus==PULL_STATUS){
                    //当前头部没有完全显示出来,隐藏头部
                    new HideHeader().execute();
                }else if(refreshStatus==RELEASE_TO_REFRESH_STATUS){
                    //开始刷新,放在异步任务中
                    new ExectueRefreshTask().execute();
                }
                break;
            }
            if(refreshStatus==RELEASE_TO_REFRESH_STATUS || refreshStatus==PULL_STATUS){
                //更新头部信息
                UpdateHeaderContent();
                // 当前正处于下拉或释放状态,要让ListView失去焦点,否则被点击的那一项会一直处于选中状态
                listView.setPressed(false);
                listView.setFocusable(false);
                listView.setFocusableInTouchMode(false);
                lastStatus=refreshStatus;
                return true;
            }
        }
        return false;
    }

    /**
     * 更新头部内容
     */
    private void UpdateHeaderContent() {
        if(lastStatus!=refreshStatus){
            switch (refreshStatus) {
            case PULL_STATUS:
                description.setText("下拉刷新");
                rotateArrow();
                break;
            case RELEASE_TO_REFRESH_STATUS:
                description.setText("释放立即刷新");
                rotateArrow();
                break;
            case REFRESHING_STATUS:
                description.setText("正在刷新...");
                arrow.clearAnimation();
                arrow.setVisibility(View.GONE);
                progressBar.setVisibility(View.VISIBLE);
                break;
            }
        }
    }

    /**
     * 旋转箭头
     */
    private void rotateArrow() {
        arrow.setVisibility(View.VISIBLE);
        progressBar.setVisibility(View.GONE);
        float pivotX = arrow.getWidth() / 2f;
        float pivotY = arrow.getHeight() / 2f;
        float fromDegrees = 0f;
        float toDegrees = 0f;
        if (refreshStatus == PULL_STATUS) {
            fromDegrees = 180f;
            toDegrees = 360f;
        } else if (refreshStatus == RELEASE_TO_REFRESH_STATUS) {
            fromDegrees = 0f;
            toDegrees = 180f;
        }
        RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees, pivotX, pivotY);
        animation.setDuration(100);
        animation.setFillAfter(true);
        arrow.startAnimation(animation);
    }


    /**
     * 判断是否允许下拉(条件:第一行item可见并处于最顶部 或者内容为空)
     */
    private void JudgeallowPull() {
        int position;
        View fistview=listView.getChildAt(0);
        if(fistview!=null){
            position=listView.getFirstVisiblePosition();
            if(position==0 && fistview.getTop()==0){
                allowpull=true;
            }else{
                //当前listview显示的item不是第一个,头部应该隐藏
                if(headermarginLayoutParams.topMargin!=hideheaderheight){
                    headermarginLayoutParams.topMargin=hideheaderheight;
                    header.setLayoutParams(headermarginLayoutParams);
                }
                allowpull=false;
            }
        }else{
            //listview 为空时 也允许下拉
            allowpull=true;
        }
    }
    //  AsyncTask定义了三种泛型类型 Params,Progress和Result。
    //
    //  Params 启动任务执行的输入参数,比如HTTP请求的URL。
    //  Progress 后台任务执行的百分比。
    //  Result 后台执行任务最终返回的结果,比如String。
    /**
     * 执行下拉刷新任务
     */
    class ExectueRefreshTask extends AsyncTask<Void, Integer,Void>{
        @Override
        protected Void doInBackground(Void... params) {
            int topmargin=headermarginLayoutParams.topMargin;
            while(true){
                topmargin+=HEADER_SCROLL_BACK;
                if(topmargin<=0){
                    topmargin=0;
                    break;
                }
                publishProgress(topmargin);
                sleep(10);
            }
            refreshStatus=REFRESHING_STATUS;
            publishProgress(0);
            if(listener!=null){
                listener.Refresh();
            }
            return null;
        }
        @Override
        protected void onProgressUpdate(Integer... values) {
            UpdateHeaderContent();
            headermarginLayoutParams.topMargin=values[0];
            header.setLayoutParams(headermarginLayoutParams);
        }
    }
    /**
     * 隐藏头部
     */
    class HideHeader extends AsyncTask<Void, Integer,Integer>{

        @Override
        protected Integer doInBackground(Void... params) {
            int topmargin=headermarginLayoutParams.topMargin;
            while(true){
                topmargin+=HEADER_SCROLL_BACK;
                if(topmargin<=hideheaderheight){
                    topmargin=hideheaderheight;
                    break;
                }
                publishProgress(topmargin);
                sleep(10);
            }
            return topmargin;
        }
        @Override
        protected void onProgressUpdate(Integer... values) {
            headermarginLayoutParams.topMargin=values[0];
            header.setLayoutParams(headermarginLayoutParams);
        }
        @Override
        protected void onPostExecute(Integer result) {
            refreshStatus=REFRESH_FINISH_STATUS;
            headermarginLayoutParams.topMargin=result;
            header.setLayoutParams(headermarginLayoutParams);
        }
    }
    /**
     * 使当前线程睡眠指定的毫秒数
     * @param i
     */
    private void sleep(int i) {
        try {
            Thread.sleep(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    interface PullToRefreshListener{
        /**
         * 刷新任务(注意刷新完之后结束刷新)
         */
        void Refresh();
    }
}

Activity:

import java.util.ArrayList;
import java.util.List;
import com.example.mytoolutils.MPullRefreshView.PullToRefreshListener;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class MPullRefreshActivity extends Activity implements OnItemClickListener, PullToRefreshListener {
    private MPullRefreshView mpullrefreshview;
    private ListView listview;
    private List<String> list;
    private MyAdapter adapter;
    private Myhandler myhandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mpull_refresh);
        mpullrefreshview=(MPullRefreshView) findViewById(R.id.mpullrefreshview);
        mpullrefreshview.setonRefreshListener(this);
        listview=(ListView) findViewById(R.id.listview);
        myhandler=new Myhandler();
        setdata();
    }
    private void setdata() {
        list=new ArrayList<String>();
        for(int a=0;a<30;a++){
            list.add(a+"");
        }
    }
    @Override
    protected void onResume() {
        super.onResume();
        adapter=new MyAdapter();
        listview.setAdapter(adapter);
        listview.setOnItemClickListener(this);
    }
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Toast.makeText(this,position+"",Toast.LENGTH_SHORT).show();
    }
    @Override
    public void Refresh() {
        try {
            //模拟联网查询数据
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Message message=new Message();
        message.arg1=1;
        myhandler.sendMessage(message);
        mpullrefreshview.finishRefreshing();
    }
    @SuppressLint("HandlerLeak")
    class Myhandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            int a= msg.arg1;
            if(a==1){
                list.add("新添加的"+list.size());
                adapter.notifyDataSetChanged();
            }
        }
    }
    @SuppressLint("InflateParams")
    class MyAdapter extends BaseAdapter{

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

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

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            Myholder myholder;
            if(convertView==null){
                myholder=new Myholder();
                convertView=LayoutInflater.from(MPullRefreshActivity.this).inflate(android.R.layout.simple_list_item_1,null);
                convertView.setTag(myholder);
            }else{
                myholder=(Myholder) convertView.getTag();
            }
            myholder.textView=(TextView) convertView.findViewById(android.R.id.text1);
            myholder.textView.setText(list.get(position));
            return convertView;
        }
    }
    class Myholder{
        TextView textView;
    }
}

activity_mpull_refresh:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.mytoolutils.MPullRefreshActivity" >

    <com.example.mytoolutils.MPullRefreshView
        android:id="@+id/mpullrefreshview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" 
        >

        <ListView
            android:id="@+id/listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:listSelector="@android:color/transparent"
            android:scrollbars="none" >
        </ListView>
    </com.example.mytoolutils.MPullRefreshView>

</merge>

mpull_refresh_view_header:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/pull_to_refresh_head"
    android:layout_width="fill_parent"
    android:layout_height="60dip" >

    <LinearLayout
        android:layout_width="200dip"
        android:layout_height="60dip"
        android:layout_centerInParent="true"
        android:orientation="horizontal" >

        <RelativeLayout
            android:layout_width="0dip"
            android:layout_height="60dip"
            android:layout_weight="3">
            <ImageView 
                android:id="@+id/arrow"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:src="@drawable/arrow"
                />
            <ProgressBar 
                android:id="@+id/progress_bar"
                android:layout_width="30dip"
                android:layout_height="30dip"
                android:layout_centerInParent="true"
                android:visibility="gone"
                />
        </RelativeLayout>

        <LinearLayout
            android:layout_width="0dip"
            android:layout_height="60dip"
            android:layout_weight="12"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/description"
                android:layout_width="fill_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:text="@string/pull_to_refresh" />
        </LinearLayout>
    </LinearLayout>

</RelativeLayout>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值