Android知识Android技术知识Android开发

android案例---下拉刷新

2017-08-09  本文已影响67人  return_toLife

通过这个文章你可以学习到:

  1. listview的使用

  2. 触摸事件监听

  3. 回调函数的简单应用

  4. 线程的使用

  5. 最后肯定是实现下拉刷新啦

先让我们看看最终效果

GIF.gif

那么我们就开始吧

1.因为要用到自定义的listview,我们这里先新建一个view继承listview
(ps:如果要在XML配置该View的话,我们至少要实现前面两个构造方法)

public class MyListView extends ListView {

    public MyListView(Context context) {
        super(context);
    }

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

    public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

2.创建一个listview的item布局,然后我们再主布局文件中使用我们自己的View
(ps:listview_item.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_vertical">

 <ImageView
     android:layout_marginLeft="10dp"
     android:id="@+id/listview_image"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:src="@mipmap/ic_launcher"/>
 <TextView
     android:paddingTop="15dp"
     android:layout_marginLeft="10dp"
     android:id="@+id/listview_text"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="111"
     android:textSize="20dp"/>
</LinearLayout>

(ps:activity_main)

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.a455.mypurefresh.MainActivity">

<com.example.a455.mypurefresh.MyListView
    android:id="@+id/myview"
    android:layout_width="368dp"
    android:layout_height="wrap_content"
    tools:layout_editor_absoluteY="0dp"
    tools:layout_editor_absoluteX="8dp">
</com.example.a455.mypurefresh.MyListView>
</android.support.constraint.ConstraintLayout>

3.我们给listview添加适配器,这里使用的simpleadapter
(ps:我这里就直接贴代码,很简单的,就是调用了三个简单的方法)

   public class MainActivity extends AppCompatActivity {

    MyListView myListView;

    SimpleAdapter simpleAdapter;

    List<Map<String,Object>> data;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findView();

        initData();

        initListView();
    }

    /**
    * @methodName: initListView
    * @Description: listview添加设配器
    * @param
    * @return
    * @throws
    */
    private void initListView() {

          simpleAdapter=new SimpleAdapter(
                this,                                //上下文
                data,                                //数据集
                R.layout.listview_item,             //item布局文件
                new String[]{"text","image"},       //map集合中的键值
                new int[]{R.id.listview_text,R.id.listview_image}  //item布局文件中的控件id

        myListView.setAdapter(simpleAdapter);
    }


    /**
    * @methodName: initData
    * @Description: 对数据初始化
    * @param
    * @return
    * @throws
    */
    private void initData() {
        data=new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            Map<String,Object> map=new HashMap<String,Object>();
            map.put("text",i);
            map.put("image",R.mipmap.ic_launcher);
            data.add(map);
        }
    }

    private void findView() {
        myListView=(MyListView)findViewById(R.id.myview);
    }

到这里我们就显示出一个列表了,前面都是配菜,现在开始主菜

4. 先创建我们的下拉头部布局文件
(ps:header.xml,这里主要注意的是progressBar中的visibility:gone
gone意味隐藏并且不占用空间)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:orientation="vertical"
            android:layout_centerInParent="true"
            android:id="@+id/layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
            <TextView
                android:id="@+id/tip"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉可刷新"/>
            <TextView
                android:layout_marginTop="5dp"
                android:id="@+id/time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>

        <ImageView
            android:id="@+id/header_image"
            android:layout_marginRight="10dp"
            android:src="@drawable/pull_down"
            android:layout_toLeftOf="@id/layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <ProgressBar
            android:id="@+id/progressbar"
            android:layout_marginRight="10dp"
            android:layout_toLeftOf="@id/layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:visibility="gone"/>
    </RelativeLayout>
</LinearLayout>

5. ok,头部文件有了,那么我们要把它加到我们自定义view的顶部
(这里我就写关键代码啦,记得在构造方法中调用该方法

  View header;
   int headerHight;
 private  void init(Context context) {
             //解析header布局文件
        header = LayoutInflater.from(context).inflate(R.layout.header, null);

        //测量header的高宽,具体可以查看MeasureSpec包
        int width = View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        int hight = View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        header.measure(width, hight);

        //获取头部的高度,用于后面隐藏我下拉
        headerHight=header.getMeasuredHeight();
        //将header添加到listview的顶部
        this.addHeaderView(header);
    }

那我们的布局现在是这样的:


111.PNG

我们用一个方法把它隐藏:


    /**
      * 根据传入的高度设置header的paddingtop
      */
    private void toPadding(int i) {
        header.setPadding(
                header.getPaddingLeft(),
                i,
                header.getPaddingRight(),
                header.getPaddingBottom()
        );
        header.invalidate();
    }

然后我们只需要在addHeaderView前面调用该方法

       //隐藏头部文件
        toPadding(-headerHight);

        //将header添加到listview的顶部
        this.addHeaderView(header);

6. 那么接下来我们就是该监听触摸下拉的事件了,这里是难点
(ps:首先添加滑动监听接口,重写它要实现的方法,定义两个变量来存储信息
不要忘了添加回调接口哟)

public class MyListView extends ListView implements AbsListView.OnScrollListener{
    int scrollState;  //当前view的滚动状态

    int firstVisibleItem; //可见的第一个item 

      .....//之前的内容省略啦
 private  void init(Context context) {
   .....//之前的内容省略啦
   this.setOnScrollListener(this);  
}
  @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        this.scrollState=scrollState;
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        this.firstVisibleItem=firstVisibleItem;
    }

然后我们再添加几个变量用来表示下拉的状态:

public class MyListView extends ListView implements AbsListView.OnScrollListener{
   int startY;     //触摸屏幕时的高度
    boolean isRemak;   //是否可以刷新
    int state=0;            //当前header状态
    final int NONE=0;     //正常状态
    final int PULL=1;     //下拉状态
    final int RELEASE=2;  //提示刷新状态
    final int REFRESHING=3; //正在刷新状态
    ......

重头戏来啦,我们重写onTouche方法:


    /*
    *触摸事件处理,分三种情况,按下,移动,抬起
    * 按下:判断当前是否为listview顶部,如果是记录按下位置
    * 移动:调用onMove方法处理移动情况
    * 抬起:如果当前状态为提示刷新状态就对header进行更新,并调用刷新内容接口
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        switch (ev.getAction()){
            case  MotionEvent.ACTION_DOWN:
                if(firstVisibleItem==0){
                    isRemak=true;
                    startY=(int)ev.getY();
                }
                break;
            case  MotionEvent.ACTION_MOVE:
                onMove(ev);      //判断state的状态
                break;
            case  MotionEvent.ACTION_UP:
                if(state==RELEASE){
                    state=REFRESHING;
                    refreshByState();//刷新header
                    //iRefreshen.onRefresh(); //刷新listview,这里是通过接口回调的方法来实现的我们先注释掉,后面再来实现
                }else if(state==PULL){
                    state=NONE;
                    isRemak=false;
                    refreshByState();
                }
                break;
        }

        return super.onTouchEvent(ev);

    }

onMove方法是监听当我们再滑动的时候,我们的state是处于什么状态

  /*
    *触摸移动的时候判断移动的位置
    * 若下拉高过或低于设定的数值,则改变提示信息
     */
    private void onMove(MotionEvent ev) {

        if(!isRemak){
            return;
        }

        int tempY=(int) ev.getY();
        int distance=tempY-startY;
        int toPadding=distance-headerHight;

        switch (state){
            case NONE:
                if(distance>0){
                    state=PULL;
                    refreshByState();
                }
                break;
            case PULL:
                toPadding(toPadding);
                if(distance>(headerHight+150) && scrollState==SCROLL_STATE_TOUCH_SCROLL){
                    state=RELEASE;
                    refreshByState();
                }
                break;
            case RELEASE:
                toPadding(toPadding);
                if(distance<(headerHight+150)){
                    state=PULL;
                    refreshByState();
                }
                break;
        }


    }

refreshByState就是我们在滑动的时候根据state的状态来更新header的高度和内容信息
(ps:这里我用的是动画将图片旋转了,小伙伴也可以用两张图片,一张下拉,一张上拉
还有控件的获取要在解析header布局文件后获取)

 /*
    *通过当前state状态改变header的显示布局
     */
    private void refreshByState() {

        switch (state){
            case NONE:
               // imageView.clearAnimation();
                toPadding(-headerHight);
                imageView.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.GONE);
                break;
            case PULL:
                imageView.clearAnimation();
                imageView.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.GONE);
                imageView.setAnimation(anim2);
                tip.setText("下拉可刷新");
                break;
            case RELEASE:
                imageView.clearAnimation();
                imageView.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.GONE);
                imageView.setAnimation(anim1);
                tip.setText("松开可刷新");
                break;
            case REFRESHING:
                toPadding(50);
                imageView.clearAnimation();
                imageView.setVisibility(View.GONE);
                progressBar.setVisibility(View.VISIBLE);
                tip.setText("正在刷新");
                break;
        }
    }

7. 到这里我们就实现了监听下拉刷新了,现在来做刷新后的事情
(ps:上面部分肯比较多,慢慢消化,乱的话可以看看源码)
我们写一个刷新后把headr布局,各种信息还原并且更新时间的方法

 /*
    *刷新完成后调用此方法重新设置参数,并设置上次刷新时间
     */
    public void refreshComplete(){
        state=NONE;
        isRemak=false;

        refreshByState();

        Date date=new Date(System.currentTimeMillis());

        SimpleDateFormat format=new SimpleDateFormat("yy年mm月dd日 HH:MM:SS");

        String time=format.format(date);

        this.time.setText(time);

    }

8. 最后一部,我们预留一个接口,让外面来完成刷新后的事情

 public class MyListView extends ListView implements AbsListView.OnScrollListener{
........
......
.....
  public void setInterface(IRefreshen iRefreshen){
        this.iRefreshen=iRefreshen;
    }

    public interface IRefreshen{
        public  void onRefresh();
    }
}

主方法去实现该接口
(PS: //这里使用handler延迟2秒来模拟刷新时候的等待)

public class MainActivity extends AppCompatActivity implements MyListView.IRefreshen{


   private void findView() {
          ......
        myListView.setInterface(this);   
    }
   @Override
    public void onRefresh() {
        Handler handler=new Handler();
   
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Map<String,Object> map=new HashMap<String,Object>();
                map.put("text","刷新");
                map.put("image",R.mipmap.ic_launcher);
                data.add(0,map);

                myListView.setAdapter(simpleAdapter);
                simpleAdapter.notifyDataSetChanged();

                myListView.refreshComplete();

            }
        },2000);
    }

至此,恭喜大家实现下拉刷新了

我来总结下思路:

  1. 创建自定义Listview布局,在主布局中使用该布局
  2. 创建header布局并添加进Listview的头部,并用paddingTop来隐藏
  3. 触摸事件的监听和处理
  4. 用回调接口,实现在MainAcitivity中刷新内容

其实总体思路很简单,难点就在于触摸的时候对位置的监听和处理,这里比较繁琐,但是慢慢理解就不会觉得很难了

这是我第一次分享,希望大家支持!!!
有发现问题的可以留言,谢谢大家观赏,你的点赞是我继续分享的动力!!!

项目github地址:https://github.com/DongDian455/Android.git

上一篇下一篇

猜你喜欢

热点阅读