Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android實現仿QQ界面刷新

android實現仿QQ界面刷新

編輯:關於Android編程

昨天想要實現一個下拉刷新的效果,本來想應該比較簡單,因為之前在慕課網看見過類似的實現,記得是在listView裡面添加footView或是添加headView,監聽手指的點擊滑動事件來控制view的顯示或是隱藏,但是自己按照上面的代碼來實現之後發現。這樣做有一點不好的地方:
它判斷是否刷新的依據是判斷listView是不是滑動到了最後一個item。只要一旦滑動到最後一列就會自動刷新數據,我們並不能控制對數據刷新的取消。

於是我想到的QQ主界面的刷新功能:先看看QQ界面的刷新功能:
 

這裡寫圖片描述

 

於是我就想實現這樣的一個功能,但在網上搜索了好久大多的都是通過上述方式來實現的。還有一種方式是通過自定義ViewGroup來實現的(原諒我是新手,對於自定義view雖然接觸了一些,仍然用不起來T_T)於是今天下午不斷測試,不斷調試,最終覺得效果有點類似QQ那樣的效果了,就迫不及待的要寫一篇博客了(≧▽≦)

畢竟第一篇博客,又是新手有什麼不對的地方還望大家指正

先來看看最終的效果:
這裡寫圖片描述


基本上和QQ的刷新很相似了,只是界面好簍的感覺。。。

所以來看看是怎麼來實現的吧

先來創建一個項目,名為ListViewTest

接著在activity_main布局文件裡面代碼如下:

<code class="language-xml hljs "><linearlayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">

    <linearlayout android:background="#FFFFFF" android:gravity="center" android:id="@+id/load_layout" android:layout_height="60dp" android:layout_width="match_parent" android:orientation="horizontal">

        <imageview android:contentdescription="@drawable/esc" android:id="@+id/main_activity_top_iv" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/esc">

        <textview android:id="@+id/main_activity_top_tv" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="下拉刷新" android:textsize="12sp">
    </textview></imageview></linearlayout>

    <listview android:id="@+id/main_activity_lv" android:layout_height="fill_parent" android:layout_width="fill_parent">
    </listview>

</linearlayout></code>

這個布局文件裡面主要有兩部分,一個是上面的LinearLayout,用於表示刷新界面的布局,裡面有一個ImageView和一個TextView用於表示刷新布局的前面的圖標和後面的提示文字。這裡我們讓它們居中顯示,因為在後面我們要對這三個控件都要進行操作,所以都要給它們加上id。
還有裡面的圖片是我從qq的安裝包裡面隨便找的。
另一部分是界面的主體內容部分,我們用一個listView來進行表示,因為listView更有代表性,尤其是在存在有手指滑動的情況下。我們也給它加上id。

好了布局文件我們寫好了,之後就是MainActivity裡面的邏輯代碼了

之後是MainActivity的內容

package com.example.listviewtest;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements OnTouchListener {

    // 測試的數據源
    private String[] strs = new String[30];
    private ListView listView;
    private ArrayAdapter adapter;

    // 頂部刷新視圖的view
    private View top;
    // 頂部刷新視圖中的文字提示
    private TextView topTV;
    // 頂部刷新試圖的圖片提示
    private ImageView topIv;
    // 頂部刷新視圖的參數,通過此參數來更改topMargin的值
    private LayoutParams topParams;

    /**
     * 頂部刷新視圖的高度,通過topmargin和這個值進行比較來判斷刷新視圖的狀態
     */
    private int topHeight = 80;

    /**
     * 當listview裡面的第一個item可見的時候,初始化此值。即當刷新視圖出現的時候手指所在的Y軸的位置
     */
    private int currentY;

    // 這三個值分別是手指按下,移動,和抬起的時候手指的Y軸位置
    private int moveY;
    private int downY;
    private int upY;

    /**
     * 這個值用於判斷
     */
    private boolean isRead = false;

    /**
     * 這個值用於判斷當前是不是正在加載數據,如果正在加載,則不對刷新視圖重新進行操作,如果沒有更新,則對刷新視圖進行操作
     */
    private boolean isLoading = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        initDatas();
        initView();
        initValues();
    }

    /**
     * 這裡使用handler來進行數據的刷新,因為過郭霖說過,在android3.0之後asyncTask變成了單隊列
     * 如果兩個操作想並發進行,就應該直接用Thread
     */
    private Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            new ScrollTask().execute(-10);
            Toast.makeText(getApplicationContext(), "數據刷新完成", Toast.LENGTH_SHORT).show();
        };
    };

    /**
     * 初始化各值
     */
    private void initValues() {
        topParams = (LayoutParams) top.getLayoutParams();
        topParams.height = topHeight;
        topParams.topMargin = -topHeight;
    }

    /**
     * 初始化界面控件,以及給控件添加監聽事件
     */
    private void initView() {
        listView = (ListView) findViewById(R.id.main_activity_lv);
        top = findViewById(R.id.load_layout);
        topTV = (TextView) findViewById(R.id.main_activity_top_tv);
        topIv = (ImageView) findViewById(R.id.main_activity_top_iv);
        adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, strs);
        listView.setAdapter(adapter);
        listView.setOnTouchListener(this);
    }

    /**
     * 初始化數據源
     */
    private void initDatas() {
        for (int i = 0; i < strs.length; i++) {
            strs[i] = String.valueOf(i);
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int actionValue = event.getAction();
        int margin = 0;
        switch (actionValue) {
        case MotionEvent.ACTION_DOWN:
            downY = (int) event.getRawY();
            break;
        case MotionEvent.ACTION_MOVE:
            //如果沒有加載數據則對刷新界面進行操作
            if (!isLoading) {
                moveY = (int) event.getRawY();
                if (listView.getFirstVisiblePosition() == 0 && !isRead) {
                    isRead = true;
                    currentY = moveY;
                }
                if (isRead && currentY >= downY) {
                    margin = -(topHeight - (moveY - currentY));
                    changeTop(margin);
                }
            }
            break;
        case MotionEvent.ACTION_UP:
            isRead = false;
            if (!isLoading) {
                upY = (int) event.getRawY();
                // 計算手指離開的時候刷新視圖的margin值
                margin = upY - currentY - topHeight;
                if (margin < -topHeight) {
                    margin = -topHeight;
                }
                if (margin > 0) {
                    margin = 0;
                }
                // 如果手指離開的時候margin小於top布局的高度的話則就不進行刷新,否則就進行刷新
                if (Math.abs(margin) == 0) {
                    topIv.setImageResource(R.drawable.qapp_center_ico_loading);
                    topTV.setText("正在加載...");
                    Load();
                    isLoading = true;
                } else if (margin > -topHeight) {
                    new ScrollTask().execute(-10);
                    isLoading = true;
                }
                break;
            }
        }
        return false;
    }

    // 加載數據
    private void Load() {
        new Thread() {
            @Override
            public void run() {
                try {
                    // 模擬異步加載數據所消耗的時間
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.sendEmptyMessage(0);
            }

        }.start();
    }

    /**
     * 根據margin的值來改變刷新視圖的狀態
     * 
     * @param margin
     */
    private void changeTop(int margin) {
        //如果margin的值大於刷新界面的寬度的話,就把它設置為刷新界面的寬度,因為不能讓整個刷新界面離開頂部
        if (margin < (-topHeight)) {
            margin = (-topHeight);
        }
        //同理,為保證整個刷新界面不離開頂部,margin也不能大於零
        if (margin > 0) {
            margin = 0;
            topTV.setText("松開進行刷新...");
            topIv.setRotation(180F);
        }
        topParams.topMargin = margin;
        top.setLayoutParams(topParams);
    }

    /**
     * 數據加載完成的時候調用,將刷新視圖初始化 並將是否正在加載數據的判斷屬性設置為false
     */
    public void loadComplete() {
        topIv.setImageResource(R.drawable.esc);
        topIv.setRotation(0F);
        topTV.setText("下拉刷新...");
        isLoading = false;
    }

    class ScrollTask extends AsyncTask {

        @Override
        protected Integer doInBackground(Integer... params) {
            int topMargin = topParams.topMargin;
            while (true) {
                topMargin = topMargin + params[0];
                if (topMargin > 0) {
                    topMargin = 0;
                    break;
                }
                if (topMargin < -topHeight) {
                    topMargin = -topHeight;
                    break;
                }
                publishProgress(topMargin);
                // 為了要有滾動效果產生,每次循環使線程睡眠20毫秒,這樣肉眼才能夠看到滾動畫面
                sleep(20);
            }
            return topMargin;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            topParams.topMargin = values[0];
            top.setLayoutParams(topParams);
        }

        @Override
        protected void onPostExecute(Integer result) {
            super.onPostExecute(result);
            topParams.topMargin = result;
            top.setLayoutParams(topParams);
            loadComplete();
        }

        /**
         * 使當前線程睡眠指定的毫秒數
         * 
         * @param i
         */
        private void sleep(int i) {
            try {
                Thread.sleep(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}

裡面的代碼有點多,我先來說一下主體思路:

將刷新界面的marginTop值設置為它的寬度的負值就能讓它隱藏 先初始化各個值,包括各個控件,以及給各個控件添加監聽事件,以及給listView初始化數據,這些不用對說。 之後的一些值有必要說明,currentY表示當滑動到頂部的時候手指所在的位置滑動過程中的值減去這個值來計算margin的值。裡面的topHeight用來表示頂部的刷新界面的高度,之後的很多判斷都要和這個值進行比較,isLoading這個值是用來判斷現在是不是正在加載數據,如果正在加載數據,則不應該再對刷新界面進行操作。isRead這個值比較尴尬,因為這是我想到的一個解決方法,因為currentY是在監聽事件的move狀態中被確定的,只能初始化一次,而Touch裡面的move狀態只要手指一動就會調用。必須有一個值來標識currentY已經被初始化了。topParams只要用它來設置刷新界面的margin的值 最後通過對手指滑動過程中的位置來計算margin的值來對刷新界面進行操作。 當確定刷新的時候調用load方法進行數據的獲取,數據獲取完後再將刷新界面隱藏 最後將隱藏的方法在一個線程裡面進行,能使它緩慢隱藏(這是抄自郭霖博客(≧▽≦))

裡面有幾個主要注意的點:判斷margin的邏輯有點混亂,還有onTouch方法一定要返回false,因為一旦返回了true的話,會導致listView無法操作,我猜測listView內部應該也實現了OnTouchListener接口,當我們設置OnTouchListener的時候在它內部會對我們傳入的listener的返回值進行判斷,如果為true就不對listView本身的監聽事件進行操作。

到這裡基本上就完成了,其中由於沒有新的Activity,也沒有進行什麼需要權限的操作,所以manifest文件裡面的代碼我們也不用修改。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved