android案例---下拉刷新

通過(guò)這個(gè)文章你可以學(xué)習(xí)到:

  1. listview的使用

  2. 觸摸事件監(jiān)聽

  3. 回調(diào)函數(shù)的簡(jiǎn)單應(yīng)用

  4. 線程的使用

  5. 最后肯定是實(shí)現(xiàn)下拉刷新啦

先讓我們看看最終效果

GIF.gif

那么我們就開始吧

1.因?yàn)橐玫阶远x的listview,我們這里先新建一個(gè)view繼承l(wèi)istview
(ps:如果要在XML配置該View的話,我們至少要實(shí)現(xiàn)前面兩個(gè)構(gòu)造方法)

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.創(chuàng)建一個(gè)listview的item布局,然后我們?cè)僦鞑季治募惺褂梦覀冏约旱腣iew
(ps:listview_item.xml,該布局可以自由擴(kuò)展,我這里就用最簡(jiǎn)單的)

<?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:我這里就直接貼代碼,很簡(jiǎn)單的,就是調(diào)用了三個(gè)簡(jiǎn)單的方法)

   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添加設(shè)配器
    * @param
    * @return
    * @throws
    */
    private void initListView() {

          simpleAdapter=new SimpleAdapter(
                this,                                //上下文
                data,                                //數(shù)據(jù)集
                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: 對(duì)數(shù)據(jù)初始化
    * @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);
    }

到這里我們就顯示出一個(gè)列表了,前面都是配菜,現(xiàn)在開始主菜

4. 先創(chuàng)建我們的下拉頭部布局文件
(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的頂部
(這里我就寫關(guān)鍵代碼啦,記得在構(gòu)造方法中調(diào)用該方法

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

        //測(cè)量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);
    }

那我們的布局現(xiàn)在是這樣的:


111.PNG

我們用一個(gè)方法把它隱藏:


    /**
      * 根據(jù)傳入的高度設(shè)置header的paddingtop
      */
    private void toPadding(int i) {
        header.setPadding(
                header.getPaddingLeft(),
                i,
                header.getPaddingRight(),
                header.getPaddingBottom()
        );
        header.invalidate();
    }

然后我們只需要在addHeaderView前面調(diào)用該方法

       //隱藏頭部文件
        toPadding(-headerHight);

        //將header添加到listview的頂部
        this.addHeaderView(header);

6. 那么接下來(lái)我們就是該監(jiān)聽觸摸下拉的事件了,這里是難點(diǎn)
(ps:首先添加滑動(dòng)監(jiān)聽接口,重寫它要實(shí)現(xiàn)的方法,定義兩個(gè)變量來(lái)存儲(chǔ)信息
不要忘了添加回調(diào)接口喲)

public class MyListView extends ListView implements AbsListView.OnScrollListener{
    int scrollState;  //當(dāng)前view的滾動(dòng)狀態(tài)

    int firstVisibleItem; //可見的第一個(gè)item 

      .....//之前的內(nèi)容省略啦
 private  void init(Context context) {
   .....//之前的內(nèi)容省略啦
   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;
    }

然后我們?cè)偬砑訋讉€(gè)變量用來(lái)表示下拉的狀態(tài):

public class MyListView extends ListView implements AbsListView.OnScrollListener{
   int startY;     //觸摸屏幕時(shí)的高度
    boolean isRemak;   //是否可以刷新
    int state=0;            //當(dāng)前header狀態(tài)
    final int NONE=0;     //正常狀態(tài)
    final int PULL=1;     //下拉狀態(tài)
    final int RELEASE=2;  //提示刷新狀態(tài)
    final int REFRESHING=3; //正在刷新狀態(tài)
    ......

重頭戲來(lái)啦,我們重寫onTouche方法:


    /*
    *觸摸事件處理,分三種情況,按下,移動(dòng),抬起
    * 按下:判斷當(dāng)前是否為listview頂部,如果是記錄按下位置
    * 移動(dòng):調(diào)用onMove方法處理移動(dòng)情況
    * 抬起:如果當(dāng)前狀態(tài)為提示刷新狀態(tài)就對(duì)header進(jìn)行更新,并調(diào)用刷新內(nèi)容接口
     */
    @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的狀態(tài)
                break;
            case  MotionEvent.ACTION_UP:
                if(state==RELEASE){
                    state=REFRESHING;
                    refreshByState();//刷新header
                    //iRefreshen.onRefresh(); //刷新listview,這里是通過(guò)接口回調(diào)的方法來(lái)實(shí)現(xiàn)的我們先注釋掉,后面再來(lái)實(shí)現(xiàn)
                }else if(state==PULL){
                    state=NONE;
                    isRemak=false;
                    refreshByState();
                }
                break;
        }

        return super.onTouchEvent(ev);

    }

onMove方法是監(jiān)聽當(dāng)我們?cè)倩瑒?dòng)的時(shí)候,我們的state是處于什么狀態(tài)

  /*
    *觸摸移動(dòng)的時(shí)候判斷移動(dòng)的位置
    * 若下拉高過(guò)或低于設(shè)定的數(shù)值,則改變提示信息
     */
    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就是我們?cè)诨瑒?dòng)的時(shí)候根據(jù)state的狀態(tài)來(lái)更新header的高度和內(nèi)容信息
(ps:這里我用的是動(dòng)畫將圖片旋轉(zhuǎn)了,小伙伴也可以用兩張圖片,一張下拉,一張上拉
還有控件的獲取要在解析header布局文件后獲取)

 /*
    *通過(guò)當(dāng)前state狀態(tài)改變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. 到這里我們就實(shí)現(xiàn)了監(jiān)聽下拉刷新了,現(xiàn)在來(lái)做刷新后的事情
(ps:上面部分肯比較多,慢慢消化,亂的話可以看看源碼)
我們寫一個(gè)刷新后把headr布局,各種信息還原并且更新時(shí)間的方法

 /*
    *刷新完成后調(diào)用此方法重新設(shè)置參數(shù),并設(shè)置上次刷新時(shí)間
     */
    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. 最后一部,我們預(yù)留一個(gè)接口,讓外面來(lái)完成刷新后的事情

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

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

主方法去實(shí)現(xiàn)該接口
(PS: //這里使用handler延遲2秒來(lái)模擬刷新時(shí)候的等待)

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);
    }

至此,恭喜大家實(shí)現(xiàn)下拉刷新了

我來(lái)總結(jié)下思路:

  1. 創(chuàng)建自定義Listview布局,在主布局中使用該布局
  2. 創(chuàng)建header布局并添加進(jìn)Listview的頭部,并用paddingTop來(lái)隱藏
  3. 觸摸事件的監(jiān)聽和處理
  4. 用回調(diào)接口,實(shí)現(xiàn)在MainAcitivity中刷新內(nèi)容

其實(shí)總體思路很簡(jiǎn)單,難點(diǎn)就在于觸摸的時(shí)候?qū)ξ恢玫谋O(jiān)聽和處理,這里比較繁瑣,但是慢慢理解就不會(huì)覺(jué)得很難了

這是我第一次分享,希望大家支持!!!
有發(fā)現(xiàn)問(wèn)題的可以留言,謝謝大家觀賞,你的點(diǎn)贊是我繼續(xù)分享的動(dòng)力!!!

Demo地址:

https://github.com/returntolife455/DemoList

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容