Android 輕鬆實現仿QQ空間下拉重新整理,android輕鬆實現

來源:互聯網
上載者:User

Android 輕鬆實現仿QQ空間下拉重新整理,android輕鬆實現

(本文講解了在Android中實現列表下拉重新整理的動態效果的過程,文末附有源碼。)

看完本文,您可以學到:
1.下拉重新整理的實現原理

2.自訂Android控制項,重寫其ListView

3.ScrollListener滾動監聽

4.Adapter適配器的使用


話不多說,先來看看:




接下來我們一步一步地實現以上的效果。


一、圖文並茂的ListViewItem

看一下這一步的:  



首先,我們要實現的是帶下拉重新整理效果的ListView。所以我們選擇自己重寫原生控制項ListView。只需要寫一個類繼承它就可以了,先不添加任何的具體實現。

RefreshListView.java:

public class RefreshListView extends ListView {public RefreshListView(Context context) {super(context);// TODO Auto-generated constructor stub}public RefreshListView(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stub}public RefreshListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stub}}

要實現圖文並茂的ListViewItem,接下去就要自己定義它Item的布局,這個可以無限發揮,我這裡就只取圖和文做一個簡單的實現:

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"    android:orientation="horizontal" >    <ImageView        android:id="@+id/image"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginLeft="10dp" />    <TextView        android:id="@+id/text"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginLeft="10dp"        android:textSize="25sp" /></LinearLayout>

在這個布局中,我就只放了一個Image和一個Text。您可以自己定義地更複雜。

然後需要我們注意的是,既然我們自己定義了ListView,那我們主介面的布局也要響應地修改了:

activity_main.xml:

<RelativeLayout 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=".MainActivity" >    <com.example.mylistviewrefresh.RefreshListView        android:id="@+id/listview"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:cacheColorHint="#00000000"        android:dividerHeight="5dip" /></RelativeLayout>
可以一眼看出我們修改了它的控制項標籤,改為我們自己定義的類的完全路徑。

最後是主角MainActivity.java,裡面的一些代碼我詳細地給了注釋。這裡要注意的是適配器的使用。

public class MainActivity extends Activity {private RefreshListView listView;private SimpleAdapter simple_adapter;private List<Map<String, Object>> list;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);listView = (RefreshListView) findViewById(R.id.listview);iniData();   //初始化資料,我們給它加20條Item// 設定SimpleAdapter監聽器/** * SimpleAdapter的五個參數的含義: * 第一個:context上下文 * 第二個:用於顯示的資料,map的list * 第三個:Item的布局,即我們自訂的那個檔案 * 第四個:與第二個參數緊密聯絡,與第五個緊密聯絡,是在map中的索引值 * 第五個:我們看到是id(int類型)的數組,這個數組裡的東西是哪裡來的?是我們自己在布局檔案中定義的,忘記的讀者可以回過頭去看一下 * 這幾個參數獨立開來可能不知道是幹嗎的,但是我覺得聯合在一起就挺好理解了。 */simple_adapter = new SimpleAdapter(MainActivity.this, list,R.layout.listview_item, new String[] { "image", "text" },new int[] { R.id.image, R.id.text });//設定適配器listView.setAdapter(simple_adapter);}// 初始化SimpleAdapter資料集private List<Map<String, Object>> iniData() {list = new ArrayList<Map<String, Object>>();for (int i = 0; i < 20; i++) {Map<String, Object> map = new HashMap<String, Object>();//解釋下這裡的資料,key對應SimpleAdapter的第三個參數,必須都包含它們。值對應第五個參數,分別是圖片和文字map.put("text", i);map.put("image", R.drawable.ic_launcher);list.add(map);}return list;}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}}

到了這一步,應該能夠實現圖片+文字的listview了吧,喝口茶,我們繼續看下去。


二、加入隱藏的Header

這裡我們要說一下下拉重新整理的實現思路了:

首先,我們平常用到的下拉重新整理,都是在下拉後螢幕上方顯示出一些之前被隱藏的控制項,類似下拉的箭頭、progress bar等等。

那我們可以直接把它們設定為不可見嗎?顯然是不可以的。因為這些空間的顯示與否,有一個漸層的過程,不是刷一下就出來的。

所以我們應該這樣做:

加入一個隱藏的布局,放在螢幕上方。根據下拉的範圍來顯示響應的控制項。

這一步,我們要實現的是加入隱藏的布局,具體怎樣根據下拉的狀態來即時調整Header的顯示狀態,我們在下文細說。


我們為了需要隱藏的header再自訂個新的布局,header.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="wrap_content"    android:orientation="vertical" >    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:paddingBottom="10dip"        android:paddingTop="10dip" >        <LinearLayout            android:id="@+id/layout"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:gravity="center"            android:orientation="vertical" >            <TextView                android:id="@+id/tip"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="下拉可以重新整理!" />            <TextView                android:id="@+id/lastupdate_time"                android:layout_width="wrap_content"                android:layout_height="wrap_content" />        </LinearLayout>        <ImageView            android:id="@+id/arrow"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_toLeftOf="@id/layout"            android:layout_marginRight="20dip"            android:src="@drawable/pull_to_refresh_arrow" />        <ProgressBar            android:id="@+id/progress"            style="?android:attr/progressBarStyleSmall"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_toLeftOf="@id/layout"            android:layout_marginRight="20dip"            android:visibility="gone" />    </RelativeLayout></LinearLayout>


這個布局中包含了提示“下拉可以重新整理”、最新更新時間、下拉式箭頭的圖片(已經預先放在drawable檔案夾中了,讀者可以自己找個圖片放進去,命名為pull_to_refresh_arrow.phg)、一個更新時才顯示的progressbar(現在是隱藏的)。

為了把這個布局加到我們定義的List,我們需要改寫之前自訂的RefreshLIstview控制項:

public class RefreshListView extends ListView {View header;// 頂部布局檔案;int headerHeight;// 頂部布局檔案的高度;public RefreshListView(Context context) {super(context);// TODO Auto-generated constructor stubinitView(context);}public RefreshListView(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stubinitView(context);}public RefreshListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stubinitView(context);}/** * 初始化介面,添加頂部布局檔案到 listview */private void initView(Context context) {LayoutInflater inflater = LayoutInflater.from(context);header = inflater.inflate(R.layout.header, null);measureView(header);headerHeight = header.getMeasuredHeight();Log.i("tag", "headerHeight = " + headerHeight);//topPadding(-headerHeight);   //這一行被我注釋了,如果你去除注釋,就可以顯示出來了this.addHeaderView(header);}/** * 通知父布局,佔用的寬,高; */private void measureView(View view) {ViewGroup.LayoutParams p = view.getLayoutParams();if (p == null) {p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);}int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);int height;int tempHeight = p.height;if (tempHeight > 0) {height = MeasureSpec.makeMeasureSpec(tempHeight,MeasureSpec.EXACTLY);} else {height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);}view.measure(width, height);}/** * 設定header布局 上邊距; */private void topPadding(int topPadding) {header.setPadding(header.getPaddingLeft(), topPadding,header.getPaddingRight(), header.getPaddingBottom());header.invalidate();}}

主要實現的是把新的header加入進去,同時把它隱藏。現在我把隱藏header的那行代碼注釋了,我們看看現在的效果:


如果去除注釋,header就被隱藏。


三、加入滾動監聽即時調整Header顯示狀態以及重新整理後添加資料

思路:

添加螢幕觸摸監聽和螢幕滾動監聽。

觸摸監聽尤其重要:  

在觸摸時記錄下觸摸座標的Y值即startY,然後在移動過程中監聽當前的Y值,根據兩者的插值判斷當前的移動距離,與一些臨界值做比較

比較之後得出當前的狀態:提示下拉狀態、提示釋放狀態、重新整理狀態。根據當前的狀態來重新整理header布局的顯示情況。

滾動監聽的作用是判斷當前是否是列表的頂端(通過判斷當前可見的第一個item的position是否為0),以及在之後判斷螢幕的滾動狀態。


另外在自訂的Listview類中定義了一個介面,在mainactivity中實現這個介面,用來對資料進行重新整理。我們在重新整理的時候用了Handler延遲了兩秒,以清晰地看到重新整理的效果。

修改後的MainActivity以及ListView:

ListView:  (裡面很多注釋,自己看著應該很好理解)

public class RefreshListView extends ListView implements OnScrollListener {View header;// 頂部布局檔案;int headerHeight;// 頂部布局檔案的高度;int firstVisibleItem;// 當前第一個可見的item的位置;int scrollState;// listview 當前滾動狀態;boolean isRemark;// 標記,當前是在listview最頂端摁下的;int startY;// 摁下時的Y值;int state;// 當前的狀態;final int NONE = 0;// 正常狀態;final int PULL = 1;// 提示下拉狀態;final int RELEASE = 2;// 提示釋放狀態;final int REFRESHING = 3;// 重新整理狀態;IRefreshListener iRefreshListener;//重新整理資料的介面public RefreshListView(Context context) {super(context);// TODO Auto-generated constructor stubinitView(context);}public RefreshListView(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stubinitView(context);}public RefreshListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stubinitView(context);}/** * 初始化介面,添加頂部布局檔案到 listview *  * @param context */private void initView(Context context) {LayoutInflater inflater = LayoutInflater.from(context);header = inflater.inflate(R.layout.header, null);measureView(header);headerHeight = header.getMeasuredHeight();Log.i("tag", "headerHeight = " + headerHeight);topPadding(-headerHeight);this.addHeaderView(header);this.setOnScrollListener(this);}/** * 通知父布局,佔用的寬,高; *  * @param view */private void measureView(View view) {ViewGroup.LayoutParams p = view.getLayoutParams();if (p == null) {p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);}int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);int height;int tempHeight = p.height;if (tempHeight > 0) {height = MeasureSpec.makeMeasureSpec(tempHeight,MeasureSpec.EXACTLY);} else {height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);}view.measure(width, height);}/** * 設定header 布局 上邊距; *  * @param topPadding */private void topPadding(int topPadding) {header.setPadding(header.getPaddingLeft(), topPadding,header.getPaddingRight(), header.getPaddingBottom());header.invalidate();}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {// TODO Auto-generated method stubthis.firstVisibleItem = firstVisibleItem;}@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {// TODO Auto-generated method stubthis.scrollState = scrollState;}/** * 對螢幕觸摸的監控, * 先判斷當前是否是在頂端。如果是在最頂端,記錄下你開始滑動的Y值 * 然後在滑動過程中(監聽到的是ACTION_MOVE),不斷地判斷當前滑動的範圍是否到達應該重新整理的程度。 * (根據當前的Y-之前的startY的值 與我們的控制項的高度之間關係來判斷) * 然後在監聽到手指鬆開時,根據當前的狀態(我們在onmove()中計算的),做相應的操作。 */@Overridepublic boolean onTouchEvent(MotionEvent ev) {// TODO Auto-generated method stubswitch (ev.getAction()) {case MotionEvent.ACTION_DOWN:if (firstVisibleItem == 0) {isRemark = true;startY = (int) ev.getY();}break;case MotionEvent.ACTION_MOVE:onMove(ev);break;case MotionEvent.ACTION_UP:if (state == RELEASE) {//即提示鬆開重新整理的狀態,一旦鬆開,進入到正在重新整理;這時候就可以載入資料了!state = REFRESHING;// 載入最新資料;refreshViewByState();iRefreshListener.onRefresh();} else if (state == PULL) {//提示下拉狀態狀態,如果放掉的話,把一切還原,什麼都沒有做state = NONE;isRemark = false;refreshViewByState();}break;}return super.onTouchEvent(ev);}/** * 判斷移動過程操作: * 如果不是頂端,不需要做任何的操作 * 否則就擷取當前的Y值,與開始的Y值做比較。 * 判斷下拉的高度,與我們定義的一些臨界值做判斷(其實這個臨界值你可以自己定義) *  * @param ev */private void onMove(MotionEvent ev) {if (!isRemark) {return;}int tempY = (int) ev.getY();int space = tempY - startY;int topPadding = space - headerHeight;switch (state) {case NONE:if (space > 0) {   state = PULL;  //正在下拉refreshViewByState();}break;case PULL:topPadding(topPadding);//如果大於一定高度,並且滾動狀態是正在滾動時,就到了鬆開可以重新整理的狀態if (space > headerHeight + 30&& scrollState == SCROLL_STATE_TOUCH_SCROLL) {state = RELEASE;refreshViewByState();}break;case RELEASE:topPadding(topPadding);//在提示鬆開重新整理時,如果你往上拖,距離小於一定高度時,提示下拉可以重新整理if (space < headerHeight + 30) {  state = PULL;refreshViewByState();} break;}}/** * 根據目前狀態,改變介面顯示; */private void refreshViewByState() {//如果要提高效能,這些應該在oncreate中寫,但是。。那裡面參數太多了,為了大家讀代碼更舒服,就寫在這裡了。TextView tip = (TextView) header.findViewById(R.id.tip);ImageView arrow = (ImageView) header.findViewById(R.id.arrow);ProgressBar progress = (ProgressBar) header.findViewById(R.id.progress);RotateAnimation anim = new RotateAnimation(0, 180,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);anim.setDuration(500);anim.setFillAfter(true);RotateAnimation anim1 = new RotateAnimation(180, 0,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);anim1.setDuration(500);anim1.setFillAfter(true);switch (state) {case NONE:     //正常狀態不顯示arrow.clearAnimation();topPadding(-headerHeight);break;case PULL:     //下拉狀態顯示箭頭,隱藏進度條,以下的狀態也類似。自己根據實際情況去修改。arrow.setVisibility(View.VISIBLE);progress.setVisibility(View.GONE);tip.setText("下拉可以重新整理!");arrow.clearAnimation();arrow.setAnimation(anim1);break;case RELEASE:  arrow.setVisibility(View.VISIBLE);progress.setVisibility(View.GONE);tip.setText("鬆開可以重新整理!");arrow.clearAnimation();arrow.setAnimation(anim);break;case REFRESHING:topPadding(50);arrow.setVisibility(View.GONE);progress.setVisibility(View.VISIBLE);tip.setText("正在重新整理...");arrow.clearAnimation();break;}}/** * 擷取完資料之後 */public void refreshComplete() {state = NONE;isRemark = false;refreshViewByState();TextView lastupdatetime = (TextView) header.findViewById(R.id.lastupdate_time);SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");Date date = new Date(System.currentTimeMillis());String time = format.format(date);lastupdatetime.setText(time);}public void setInterface(IRefreshListener iRefreshListener){this.iRefreshListener = iRefreshListener;}/** * 重新整理資料介面 * @author Administrator */public interface IRefreshListener{public void onRefresh();}}

MainActivity: (添加了介面回調,即在listview中調用main的添加資料的方法)

public class MainActivity extends Activity  implements IRefreshListener {private RefreshListView listView;private SimpleAdapter simple_adapter;private List<Map<String, Object>> list;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);listView = (RefreshListView) findViewById(R.id.listview);iniData();   //初始化資料,我們給它加20條Item// 設定SimpleAdapter監聽器/** * SimpleAdapter的五個參數的含義: * 第一個:context上下文 * 第二個:用於顯示的資料,map的list * 第三個:Item的布局,即我們自訂的那個檔案 * 第四個:與第二個參數緊密聯絡,與第五個緊密聯絡,是在map中的索引值 * 第五個:我們看到是id(int類型)的數組,這個數組裡的東西是哪裡來的?是我們自己在布局檔案中定義的,忘記的讀者可以回過頭去看一下 * 這幾個參數獨立開來可能不知道是幹嗎的,但是我覺得聯合在一起就挺好理解了。 */simple_adapter = new SimpleAdapter(MainActivity.this, list,R.layout.listview_item, new String[] { "image", "text" },new int[] { R.id.image, R.id.text });//設定適配器listView.setAdapter(simple_adapter);//設定更新資料的介面        listView.setInterface(this);}// 初始化SimpleAdapter資料集private List<Map<String, Object>> iniData() {list = new ArrayList<Map<String, Object>>();for (int i = 0; i < 20; i++) {Map<String, Object> map = new HashMap<String, Object>();//解釋下這裡的資料,key對應SimpleAdapter的第三個參數,必須都包含它們。值對應第五個參數,分別是圖片和文字map.put("text", i);map.put("image", R.drawable.ic_launcher);list.add(map);}return list;}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}/** * 介面回調,在RefreshListView中可以調用此方法進行資料添加。 */@Overridepublic void onRefresh() {// TODO 自動產生的方法存根Handler handler = new Handler();handler.postDelayed(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubMap<String, Object> map = new HashMap<String, Object>();map.put("text", "滾動添加 ");map.put("image", R.drawable.ic_launcher);list.add(0, map);listView.setAdapter(simple_adapter);simple_adapter.notifyDataSetChanged();listView.refreshComplete();}}, 2000);}}

到了這裡,我們就實現了文章開頭的了。


========================================

寫在後面:

原始碼已上傳到我的Github,或者到網盤下載。

任何問題,歡迎留言交流!


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.