編輯:關於Android編程
關於實現ListView下拉刷新和加載更多的實現,我想網上一搜就一堆。不過我就沒發現比較實用的,要不就是實現起來太復雜,要不就是不健全的。因為小巫近期要開發新浪微博客戶端,需要實現ListView的下拉刷新,所以就想把這個UI整合到項目當中去,這裡只是一個demo,可以根據項目的需要進行修改。
自定義ListView:
package com.markupartist.android.widget; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import com.markupartist.android.example.pulltorefresh.R; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ImageView; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; /** * 2013/8/13 * 自定義ListView,實現OnScrollListener接口 * 此ListView是為實現"下拉刷新"和"上拉加載更多"而定制的,具體效果可參考新浪微博、騰訊微博 * @author wwj * */ public class PullToRefreshListView extends ListView implements OnScrollListener { private static final int TAP_TO_REFRESH = 1; //(未刷新) private static final int PULL_TO_REFRESH = 2; // 下拉刷新 private static final int RELEASE_TO_REFRESH = 3; // 釋放刷新 private static final int REFRESHING = 4; // 正在刷新 private static final int TAP_TO_LOADMORE = 5; // 未加載更多 private static final int LOADING = 6; // 正在加載 private static final String TAG = "PullToRefreshListView"; private OnRefreshListener mOnRefreshListener; // 刷新監聽器 /** * Listener that will receive notifications every time the list scrolls. */ private OnScrollListener mOnScrollListener; // 列表滾動監聽器 private LayoutInflater mInflater; // 用於加載布局文件 private RelativeLayout mRefreshHeaderView; // 刷新視圖(也就是頭部那部分) private TextView mRefreshViewText; // 刷新提示文本 private ImageView mRefreshViewImage; // 刷新向上向下的那個圖片 private ProgressBar mRefreshViewProgress; // 這裡是圓形進度條 private TextView mRefreshViewLastUpdated; // 最近更新的文本 private RelativeLayout mLoadMoreFooterView; // 加載更多 private TextView mLoadMoreText; // 提示文本 private ProgressBar mLoadMoreProgress; // 加載更多進度條 private int mCurrentScrollState; // 當前滾動位置 private int mRefreshState; // 刷新狀態 private int mLoadState; // 加載狀態 private RotateAnimation mFlipAnimation; // 下拉動畫 private RotateAnimation mReverseFlipAnimation; // 恢復動畫 private int mRefreshViewHeight; // 刷新視圖高度 private int mRefreshOriginalTopPadding; // 原始上部間隙 private int mLastMotionY; // 記錄點擊位置 public PullToRefreshListView(Context context) { super(context); init(context); } public PullToRefreshListView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { // Load all of the animations we need in code rather than through XML /** 定義旋轉動畫**/ // 參數:1.旋轉開始的角度 2.旋轉結束的角度 3. X軸伸縮模式 4.X坐標的伸縮值 5.Y軸的伸縮模式 6.Y坐標的伸縮值 mFlipAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); mFlipAnimation.setInterpolator(new LinearInterpolator()); mFlipAnimation.setDuration(250); // 設置持續時間 mFlipAnimation.setFillAfter(true); // 動畫執行完是否停留在執行完的狀態 mReverseFlipAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); mReverseFlipAnimation.setInterpolator(new LinearInterpolator()); mReverseFlipAnimation.setDuration(250); mReverseFlipAnimation.setFillAfter(true); // 獲取LayoutInflater實例對象 mInflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE); // 加載下拉刷新的頭部視圖 mRefreshHeaderView = (RelativeLayout) mInflater.inflate( R.layout.pull_to_refresh_header, this, false); mRefreshViewText = (TextView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_text); mRefreshViewImage = (ImageView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_image); mRefreshViewProgress = (ProgressBar) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_progress); mRefreshViewLastUpdated = (TextView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_updated_at); mLoadMoreFooterView = (RelativeLayout) mInflater.inflate( R.layout.loadmore_footer, this, false); mLoadMoreText = (TextView) mLoadMoreFooterView.findViewById(R.id.loadmore_text); mLoadMoreProgress = (ProgressBar) mLoadMoreFooterView.findViewById(R.id.loadmore_progress); mRefreshViewImage.setMinimumHeight(50); // 設置圖片最小高度 mRefreshHeaderView.setOnClickListener(new OnClickRefreshListener()); mRefreshOriginalTopPadding = mRefreshHeaderView.getPaddingTop(); mLoadMoreFooterView.setOnClickListener(new OnClickLoadMoreListener()); mRefreshState = TAP_TO_REFRESH; // 初始刷新狀態 mLoadState = TAP_TO_LOADMORE; addHeaderView(mRefreshHeaderView); // 增加頭部視圖 addFooterView(mLoadMoreFooterView); // 增加尾部視圖 super.setOnScrollListener(this); measureView(mRefreshHeaderView); // 測量視圖 mRefreshViewHeight = mRefreshHeaderView.getMeasuredHeight(); // 得到視圖的高度 } @Override protected void onAttachedToWindow() { setSelection(1); // 設置當前選中的項 } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); setSelection(1); } /** * Set the listener that will receive notifications every time the list * scrolls. * * @param l The scroll listener. */ @Override public void setOnScrollListener(AbsListView.OnScrollListener l) { mOnScrollListener = l; } /** * Register a callback to be invoked when this list should be refreshed. * 注冊監聽器 * @param onRefreshListener The callback to run. */ public void setOnRefreshListener(OnRefreshListener onRefreshListener) { mOnRefreshListener = onRefreshListener; } /** * Set a text to represent when the list was last updated. * 設置一個文本來表示最近更新的列表,顯示的是最近更新列表的時間 * @param lastUpdated Last updated at. */ public void setLastUpdated(CharSequence lastUpdated) { if (lastUpdated != null) { mRefreshViewLastUpdated.setVisibility(View.VISIBLE); mRefreshViewLastUpdated.setText("更新於: " + lastUpdated); } else { mRefreshViewLastUpdated.setVisibility(View.GONE); } } @Override public boolean onTouchEvent(MotionEvent event) { final int y = (int) event.getY(); // 獲取點擊位置的Y坐標 switch (event.getAction()) { case MotionEvent.ACTION_UP: // 手指抬起 if (!isVerticalScrollBarEnabled()) { setVerticalScrollBarEnabled(true); } if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) { if ((mRefreshHeaderView.getBottom() > mRefreshViewHeight || mRefreshHeaderView.getTop() >= 0) && mRefreshState == RELEASE_TO_REFRESH) { // Initiate the refresh mRefreshState = REFRESHING; // 刷新狀態 prepareForRefresh(); onRefresh(); } else if (mRefreshHeaderView.getBottom() < mRefreshViewHeight || mRefreshHeaderView.getTop() < 0) { // Abort refresh and scroll down below the refresh view resetHeader(); setSelection(1); } } break; case MotionEvent.ACTION_DOWN: mLastMotionY = y; break; case MotionEvent.ACTION_MOVE: applyHeaderPadding(event); break; } return super.onTouchEvent(event); } private void applyHeaderPadding(MotionEvent ev) { final int historySize = ev.getHistorySize(); // Workaround for getPointerCount() which is unavailable in 1.5 // (it's always 1 in 1.5) int pointerCount = 1; try { Method method = MotionEvent.class.getMethod("getPointerCount"); pointerCount = (Integer)method.invoke(ev); } catch (NoSuchMethodException e) { pointerCount = 1; } catch (IllegalArgumentException e) { throw e; } catch (IllegalAccessException e) { System.err.println("unexpected " + e); } catch (InvocationTargetException e) { System.err.println("unexpected " + e); } for (int h = 0; h < historySize; h++) { for (int p = 0; p < pointerCount; p++) { if (mRefreshState == RELEASE_TO_REFRESH) { if (isVerticalFadingEdgeEnabled()) { setVerticalScrollBarEnabled(false); } int historicalY = 0; try { // For Android > 2.0 Method method = MotionEvent.class.getMethod( "getHistoricalY", Integer.TYPE, Integer.TYPE); historicalY = ((Float) method.invoke(ev, p, h)).intValue(); } catch (NoSuchMethodException e) { // For Android < 2.0 historicalY = (int) (ev.getHistoricalY(h)); } catch (IllegalArgumentException e) { throw e; } catch (IllegalAccessException e) { System.err.println("unexpected " + e); } catch (InvocationTargetException e) { System.err.println("unexpected " + e); } // Calculate the padding to apply, we divide by 1.7 to // simulate a more resistant effect during pull. int topPadding = (int) (((historicalY - mLastMotionY) - mRefreshViewHeight) / 1.7); // 設置上、下、左、右四個位置的間隙間隙 mRefreshHeaderView.setPadding( mRefreshHeaderView.getPaddingLeft(), topPadding, mRefreshHeaderView.getPaddingRight(), mRefreshHeaderView.getPaddingBottom()); } } } } /** * Sets the header padding back to original size. * 設置頭部填充會原始大小 */ private void resetHeaderPadding() { mRefreshHeaderView.setPadding( mRefreshHeaderView.getPaddingLeft(), mRefreshOriginalTopPadding, mRefreshHeaderView.getPaddingRight(), mRefreshHeaderView.getPaddingBottom()); } /** * Resets the header to the original state. * 重新設置頭部為原始狀態 */ private void resetHeader() { if (mRefreshState != TAP_TO_REFRESH) { mRefreshState = TAP_TO_REFRESH; resetHeaderPadding(); // Set refresh view text to the pull label mRefreshViewText.setText(R.string.pull_to_refresh_tap_label); // Replace refresh drawable with arrow drawable mRefreshViewImage.setImageResource(R.drawable.ic_pulltorefresh_arrow); // Clear the full rotation animation mRefreshViewImage.clearAnimation(); // Hide progress bar and arrow. mRefreshViewImage.setVisibility(View.GONE); mRefreshViewProgress.setVisibility(View.GONE); } } /** * 重設ListView尾部視圖為初始狀態 */ private void resetFooter() { if(mLoadState != TAP_TO_LOADMORE) { mLoadState = TAP_TO_LOADMORE; // 進度條設置為不可見 mLoadMoreProgress.setVisibility(View.GONE); // 按鈕的文本替換為“加載更多” mLoadMoreText.setText(R.string.loadmore_label); } } /** * 測量視圖的大小 * @param child */ private void measureView(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // When the refresh view is completely visible, change the text to say // "Release to refresh..." and flip the arrow drawable. if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL && mRefreshState != REFRESHING) { if (firstVisibleItem == 0) { // 如果第一個可見條目為0 mRefreshViewImage.setVisibility(View.VISIBLE); // 讓指示箭頭變得可見 /**如果頭部視圖相對與父容器的位置大於其自身高度+20或者頭部視圖的頂部位置>0,並且要在刷新狀態不等於"釋放以刷新"**/ if ((mRefreshHeaderView.getBottom() > mRefreshViewHeight + 20 || mRefreshHeaderView.getTop() >= 0) && mRefreshState != RELEASE_TO_REFRESH) { mRefreshViewText.setText(R.string.pull_to_refresh_release_label);// 設置刷新文本為"Release to refresh..." mRefreshViewImage.clearAnimation(); // 清除動畫 mRefreshViewImage.startAnimation(mFlipAnimation); // 啟動動畫 mRefreshState = RELEASE_TO_REFRESH; // 更改刷新狀態為“釋放以刷新" } else if (mRefreshHeaderView.getBottom() < mRefreshViewHeight + 20 && mRefreshState != PULL_TO_REFRESH) { mRefreshViewText.setText(R.string.pull_to_refresh_pull_label);// 設置刷新文本為"Pull to refresh..." if (mRefreshState != TAP_TO_REFRESH) { mRefreshViewImage.clearAnimation(); mRefreshViewImage.startAnimation(mReverseFlipAnimation); } mRefreshState = PULL_TO_REFRESH; } } else { mRefreshViewImage.setVisibility(View.GONE); // 讓刷新箭頭不可見 resetHeader(); // 重新設置頭部為原始狀態 } } else if (mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0 && mRefreshState != REFRESHING) { setSelection(1); } if (mOnScrollListener != null) { mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { mCurrentScrollState = scrollState; if (mOnScrollListener != null) { mOnScrollListener.onScrollStateChanged(view, scrollState); } } /**為刷新做准備**/ public void prepareForRefresh() { resetHeaderPadding(); mRefreshViewImage.setVisibility(View.GONE); // 去掉刷新的箭頭 // We need this hack, otherwise it will keep the previous drawable. mRefreshViewImage.setImageDrawable(null); mRefreshViewProgress.setVisibility(View.VISIBLE); // 圓形進度條變為可見 // Set refresh view text to the refreshing label mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label); mRefreshState = REFRESHING; } /**為加載更多做准備**/ public void prepareForLoadMore() { mLoadMoreProgress.setVisibility(View.VISIBLE); mLoadMoreText.setText(R.string.loading_label); mLoadState = LOADING; } public void onRefresh() { Log.d(TAG, "onRefresh"); if (mOnRefreshListener != null) { mOnRefreshListener.onRefresh(); } } public void OnLoadMore() { Log.d(TAG, "onLoadMore"); if(mOnRefreshListener != null) { mOnRefreshListener.onLoadMore(); } } /** * Resets the list to a normal state after a refresh. * @param lastUpdated Last updated at. */ public void onRefreshComplete(CharSequence lastUpdated) { setLastUpdated(lastUpdated); // 顯示更新時間 onRefreshComplete(); } /** * Resets the list to a normal state after a refresh. */ public void onRefreshComplete() { Log.d(TAG, "onRefreshComplete"); resetHeader(); // If refresh view is visible when loading completes, scroll down to // the next item. if (mRefreshHeaderView.getBottom() > 0) { invalidateViews(); setSelection(1); } } public void onLoadMoreComplete() { Log.d(TAG, "onLoadMoreComplete"); resetFooter(); } /** * Invoked when the refresh view is clicked on. This is mainly used when * there's only a few items in the list and it's not possible to drag the * list. * 點擊刷新 */ private class OnClickRefreshListener implements OnClickListener { @Override public void onClick(View v) { if (mRefreshState != REFRESHING) { prepareForRefresh(); onRefresh(); } } } /** * * @author wwj * 加載更多 */ private class OnClickLoadMoreListener implements OnClickListener { @Override public void onClick(View v) { if(mLoadState != LOADING) { prepareForLoadMore(); OnLoadMore(); } } } /** * Interface definition for a callback to be invoked when list should be * refreshed. * 接口定義一個回調方法當列表應當被刷新 */ public interface OnRefreshListener { /** * Called when the list should be refreshed. * 當列表應當被刷新是調用這個方法 *
* A call to {@link PullToRefreshListView #onRefreshComplete()} is * expected to indicate that the refresh has completed. */ public void onRefresh(); public void onLoadMore(); } }
package com.markupartist.android.example.pulltorefresh; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.LinkedList; import android.app.Activity; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.widget.ArrayAdapter; import com.markupartist.android.widget.PullToRefreshListView; import com.markupartist.android.widget.PullToRefreshListView.OnRefreshListener; public class PullToRefreshActivity extends Activity { private LinkedListmListItems; public static PullToRefreshListView weiboListView; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.pull_to_refresh); weiboListView = (PullToRefreshListView) findViewById(R.id.weibolist); // Set a listener to be invoked when the list should be refreshed. weiboListView.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { // Do work to refresh the list here. new GetDataTask(PullToRefreshActivity.this, 0).execute(); } @Override public void onLoadMore() { new GetDataTask(PullToRefreshActivity.this, 1).execute(); } }); mListItems = new LinkedList (); mListItems.addAll(Arrays.asList(mStrings)); ArrayAdapter adapter = new ArrayAdapter (this, android.R.layout.simple_list_item_1, mListItems); weiboListView.setAdapter(adapter); } private class GetDataTask extends AsyncTask { private Context context; private int index; public GetDataTask(Context context, int index) { this.context = context; this.index = index; } @Override protected String[] doInBackground(Void... params) { // Simulates a background job. try { Thread.sleep(2000); } catch (InterruptedException e) { ; } return mStrings; } @Override protected void onPostExecute(String[] result) { if (index == 0) { // 將字符串“Added after refresh”添加到頂部 mListItems.addFirst("Added after refresh..."); SimpleDateFormat format = new SimpleDateFormat( "yyyy年MM月dd日 HH:mm"); String date = format.format(new Date()); // Call onRefreshComplete when the list has been refreshed. weiboListView.onRefreshComplete(date); } else if (index == 1) { mListItems.addLast("Added after loadmore..."); weiboListView.onLoadMoreComplete(); } super.onPostExecute(result); } } public static String[] mStrings = { "一條微博", "兩條微博", "三條微博", "四條微博", "五條微博", "六條微博", "七條微博", "八條微博", "九條微博", "十條微博", "十一條微博", "十二條微博" }; }
/2013.08.22_PullToRefresh_ListView_Demo/res/layout/pull_to_refresh_header.xml
/2013.08.22_PullToRefresh_ListView_Demo/res/layout/loadmore_footer.xml
Android UI之FrameLayout(幀布局)說明:幀布局會為每個包含其中的組件開辟一個空白區域(稱為幀),這些幀是一層層疊加在一起的,有點類似於一層層覆蓋貼上去
隨著現在手機硬件不斷的提升,分辨率提高手機的安裝包也是越來越大了。當年NOKIA,MOTO時代,一個手機APP如果有1MB那都是算大的,2MB已經不得了了。雖然網
接上面的UDP,本篇主要討論如何在局域網中搜索所有的設備,這個需求在物聯網應用的比較多,也比較綜合,特把名字加在標題中了。最後一塊是網絡編程的常見問題。3.6 實例:在局
本文要解決在側滑菜單右邊加個文本框,並能實現文本的上下滑動和菜單的左右滾動。這裡推薦可以好好看看android的觸摸事件的分發機制,這裡我就不詳細講了,我只講講這個應用。