android尽管定义了种类很丰富的控件。可是有的时候这些自己定义的控件还是不能满足我的要求,为了可以适配很多其它的需求,我们须要在原有的基础上进行自己定义控件。
今天我向大家介绍的就是android中最常见的刷新类控件。由于我们近期正在參加一个项目。在项目组长的带领下。我学到了非常多的东西,这对我的android技术的提升非常大,定义一个自己定义控件可能不是非常难。可是怎样让这个自己定义控件更加有效、更加高速地执行。
首先我们须要建立一个自己定义控件类:
package com.example.ui.widget; import com.example.androidtest.R; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; 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.LinearLayout; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; /** * 下拉刷新控件 * * @author JeffLee * @version 1.0 * @created 2014-10-21 */ public class PullToRefreshListView extends ListView implements OnScrollListener { private final static String TAG = "PullToRefreshListView"; private static final int MAX_Y_OVERSCROLL_DISTANCE = 100; private Context mContext; private int mMaxYOverscrollDistance; // 下拉刷新标志 private final static int PULL_To_REFRESH = 0; // 松开刷新标志 private final static int RELEASE_To_REFRESH = 1; // 正在刷新标志 public final static int REFRESHING = 2; // 刷新完毕标志 private final static int DONE = 3; private LayoutInflater inflater; private LinearLayout headView; private TextView tipsTextview; private TextView lastUpdatedTextView; private ImageView arrowImageView; private ProgressBar progressBar; // 用来设置箭头图标动画效果 private RotateAnimation animation; private RotateAnimation reverseAnimation; // 用于保证startY的值在一个完整的touch事件中仅仅被记录一次 private boolean isRecored; private int headContentWidth; private int headContentHeight; private int headContentOriginalTopPadding; private int startY; private int firstItemIndex; private int currentScrollState; private int state; private boolean isBack; public OnRefreshListener refreshListener; public OnLoadMoreListener loadMoreListener; // -- footer view private PullToRefreshFooter mFooterView; private boolean mEnablePullLoad; private boolean mPullLoading = false; private boolean mIsFooterReady = false; private float mFirstY = -1; // save event y private boolean bloading = false; 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); } public int getState() { return state; } private void init(Context context) { mContext = context; final DisplayMetrics metrics = mContext.getResources() .getDisplayMetrics(); final float density = metrics.density; mMaxYOverscrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE); // 设置滑动效果 animation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); animation.setInterpolator(new LinearInterpolator()); animation.setDuration(200); animation.setFillAfter(true); reverseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reverseAnimation.setInterpolator(new LinearInterpolator()); reverseAnimation.setDuration(200); reverseAnimation.setFillAfter(true); inflater = LayoutInflater.from(context); headView = (LinearLayout) inflater.inflate( R.layout.pull_to_refresh_head, null); arrowImageView = (ImageView) headView .findViewById(R.id.head_arrowImageView); // arrowImageView.setMinimumWidth(50); // arrowImageView.setMinimumHeight(50); progressBar = (ProgressBar) headView .findViewById(R.id.head_progressBar); tipsTextview = (TextView) headView.findViewById(R.id.head_tipsTextView); lastUpdatedTextView = (TextView) headView .findViewById(R.id.head_lastUpdatedTextView); headContentOriginalTopPadding = headView.getPaddingTop(); measureView(headView); headContentHeight = headView.getMeasuredHeight(); headContentWidth = headView.getMeasuredWidth(); headView.setPadding(headView.getPaddingLeft(), -1 * headContentHeight, headView.getPaddingRight(), headView.getPaddingBottom()); headView.invalidate(); addHeaderView(headView); // init footer view mFooterView = new PullToRefreshFooter(context); setOnScrollListener(this); } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); } // 须要有载入很多其它功能时候的设置adapter的方式 public void setLoadMoreAdapter(ListAdapter adapter) { setAdapter(adapter); addFooterView(); } public void addFooterView() { if (mIsFooterReady == false) { mIsFooterReady = true; mFooterView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 不在下拉刷新状态,以及没有所有载入,且不在载入很多其它的状态下进行点击刷新 if (state != REFRESHING && !mPullLoading) { // startLoadMore(); } } }); addFooterView(mFooterView); } } /** * stop load more, reset footer view. */ // public void stopLoadMore() { // if (mPullLoading == true) { // mPullLoading = false; // mFooterView.setState(XListViewFooter.STATE_NORMAL); // } // } public void completeLoadMore(int status) { if (mPullLoading == true) { state = PULL_To_REFRESH; mPullLoading = false; // mFooterView.setState(status); } mFooterView.setState(status); } public void setFooterState(int status) { mFooterView.setState(status); } public int getFooterState() { return mFooterView.getState(); } private void startLoadMore() { // 设置了载入很多其它的监听器,以及载入很多其它状态不是数据载入完毕,和为空数据的时候。都能够进行上滑刷新 if (loadMoreListener != null && mFooterView.getState() != PullToRefreshFooter.STATE_LOADFULL && mFooterView.getState() != PullToRefreshFooter.STATE_NULL) { mPullLoading = true; state = REFRESHING; mFooterView.setState(PullToRefreshFooter.STATE_LOADING); loadMoreListener.onLoadMore(); } } public void settingOnScroll(AbsListView view, int firstVisiableItem, int visibleItemCount, int totalItemCount) { firstItemIndex = firstVisiableItem; // if (firstVisiableItem + visibleItemCount >= // totalItemCount){//currentScrollState == SCROLL_STATE_TOUCH_SCROLL // if (state != REFRESHING // && ! mPullLoading) { // startLoadMore(); // } // } } public void settingOnScrollStateChanged(AbsListView view, int scrollState) { currentScrollState = scrollState; if (scrollState == SCROLL_STATE_IDLE && getCount() > 0) { // 推断滚动究竟部 if (view.getLastVisiblePosition() == (view.getCount() - 1)) { if (state != REFRESHING && !mPullLoading) { startLoadMore(); } } } } @Override public void onScroll(AbsListView view, int firstVisiableItem, int visibleItemCount, int totalItemCount) { settingOnScroll(view, firstVisiableItem, visibleItemCount, totalItemCount); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { settingOnScrollStateChanged(view, scrollState); } public final boolean getLoadingState() { return bloading; } public final void setLoadingState(boolean bloading) { // Log.i(TAG, "setLoadingState bloading =" + bloading); this.bloading = bloading; } @Override public boolean onTouchEvent(MotionEvent event) { if (mFirstY == -1) { mFirstY = event.getRawY(); } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (firstItemIndex == 0 && !isRecored) { startY = (int) event.getY(); isRecored = true; } break; case MotionEvent.ACTION_CANCEL:// 失去焦点&取消动作 case MotionEvent.ACTION_UP: if (state != REFRESHING) { if (state == DONE) { // 当前-抬起-ACTION_UP:DONE什么都不做 } else if (state == PULL_To_REFRESH) { // 当前-抬起-ACTION_UP:PULL_To_REFRESH-->DONE-由下拉刷新状态到刷新完毕状态 state = DONE; changeHeaderViewByState(); } else if (state == RELEASE_To_REFRESH) { // 当前-抬起-ACTION_UP:RELEASE_To_REFRESH-->REFRESHING-由松开刷新状态,到刷新完毕状态 state = REFRESHING; changeHeaderViewByState(); onRefresh(); } final float deltaY = event.getRawY() - mFirstY; if ((deltaY < 0) && !mPullLoading && getLastVisiblePosition() == getCount() - 1) { startLoadMore(); } mFirstY = -1; // reset } isRecored = false; isBack = false; break; case MotionEvent.ACTION_MOVE: int tempY = (int) event.getY(); if (!isRecored && firstItemIndex == 0) { isRecored = true; startY = tempY; } // 假设正在载入,则不能进行刷新操作 if (bloading) { break; } // Log.i(TAG,"tempY =" + tempY +";startY = "+startY); if (state != REFRESHING && isRecored) { // 能够松开刷新了 if (state == RELEASE_To_REFRESH) { // 往上推,推到屏幕足够掩盖head的程度。但还没有所有掩盖 if ((tempY - startY < headContentHeight + 20) && (tempY - startY) > 0) { state = PULL_To_REFRESH; changeHeaderViewByState(); } else if (tempY - startY <= 0) { // 一下子推到顶 state = DONE; changeHeaderViewByState(); } else { // 往下拉,或者还没有上推到屏幕顶部掩盖head // 不用进行特别的操作,仅仅用更新paddingTop的值即可了 } } else if (state == PULL_To_REFRESH) { // 还没有到达显示松开刷新的时候,DONE或者是PULL_To_REFRESH状态 // 下拉到能够进入RELEASE_TO_REFRESH的状态 if (tempY - startY >= headContentHeight + 20) { state = RELEASE_To_REFRESH; isBack = true; changeHeaderViewByState(); } // 上推到顶了 else if (tempY - startY <= 0) { state = DONE; changeHeaderViewByState(); } } else if (state == DONE) { // done状态下 if (tempY - startY > 0) { state = PULL_To_REFRESH; changeHeaderViewByState(); } } // 更新headView的size if (state == PULL_To_REFRESH) { int topPadding = ((-1 * headContentHeight + (tempY - startY))); headView.setPadding(headView.getPaddingLeft(), topPadding, headView.getPaddingRight(), headView.getPaddingBottom()); headView.invalidate(); } // 更新headView的paddingTop if (state == RELEASE_To_REFRESH) { int topPadding = ((tempY - startY - headContentHeight)); if (topPadding > mMaxYOverscrollDistance) topPadding = mMaxYOverscrollDistance; headView.setPadding(headView.getPaddingLeft(), topPadding, headView.getPaddingRight(), headView.getPaddingBottom()); headView.invalidate(); } } break; default: break; } if (state == RELEASE_To_REFRESH) { return true; } return super.onTouchEvent(event); } // 当状态改变时候,调用该方法。以更新界面 private void changeHeaderViewByState() { switch (state) { case RELEASE_To_REFRESH: arrowImageView.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); tipsTextview.setVisibility(View.VISIBLE); lastUpdatedTextView.setVisibility(View.VISIBLE); arrowImageView.clearAnimation(); arrowImageView.startAnimation(animation); tipsTextview.setText(R.string.pull_to_refresh_release_label); break; case PULL_To_REFRESH: progressBar.setVisibility(View.GONE); tipsTextview.setVisibility(View.VISIBLE); lastUpdatedTextView.setVisibility(View.VISIBLE); arrowImageView.clearAnimation(); arrowImageView.setVisibility(View.VISIBLE); if (isBack) { isBack = false; arrowImageView.clearAnimation(); arrowImageView.startAnimation(reverseAnimation); } tipsTextview.setText(R.string.pull_to_refresh_pull_label); break; case REFRESHING: headView.setPadding(headView.getPaddingLeft(), headContentOriginalTopPadding, headView.getPaddingRight(), headView.getPaddingBottom()); headView.invalidate(); progressBar.setVisibility(View.VISIBLE); arrowImageView.clearAnimation(); arrowImageView.setVisibility(View.GONE); tipsTextview.setText(R.string.pull_to_refresh_refreshing_label); lastUpdatedTextView.setVisibility(View.GONE); break; case DONE: { headView.setPadding(headView.getPaddingLeft(), -1 * headContentHeight, headView.getPaddingRight(), headView.getPaddingBottom()); progressBar.setVisibility(View.GONE); arrowImageView.clearAnimation(); // 此处更换图标 // arrowImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow); Bitmap arrow = BitmapFactory.decodeResource(getResources(), R.drawable.ic_pulltorefresh_arrow); Drawable background = new BitmapDrawable(arrow); arrowImageView.setBackgroundDrawable(background); tipsTextview.setText(R.string.pull_to_refresh_pull_label); lastUpdatedTextView.setVisibility(View.VISIBLE); headView.invalidate(); } break; } } // 点击刷新 public void clickRefresh() { setSelection(0); state = REFRESHING; changeHeaderViewByState(); onRefresh(); } public void setOnRefreshListener(OnRefreshListener refreshListener) { this.refreshListener = refreshListener; } public void setOnLoadMoreListener(OnLoadMoreListener loadMoreListener) { this.loadMoreListener = loadMoreListener; } public interface OnRefreshListener { public void onRefresh(); } public interface OnLoadMoreListener { public void onLoadMore(); } public void onRefreshComplete(String update) { lastUpdatedTextView.setText(update); onRefreshComplete(); } public void firstRefreshing() { state = REFRESHING; headView.setPadding(0, 10, 0, 0); progressBar.setVisibility(View.VISIBLE); arrowImageView.clearAnimation(); arrowImageView.setVisibility(View.GONE); tipsTextview.setText(R.string.pull_to_refresh_refreshing_label); lastUpdatedTextView.setVisibility(View.GONE); } /** * stop refresh, reset header view. */ public void stopRefresh() { state = PULL_To_REFRESH; changeHeaderViewByState(); } public void onRefreshComplete() { state = DONE; changeHeaderViewByState(); setSelection(0); } public void onRefreshComplete(int loadMoreStatus) { onRefreshComplete(); mFooterView.setState(loadMoreStatus); } private void onRefresh() { if (refreshListener != null) { refreshListener.onRefresh(); } } // 计算headView的width及height值 private void measureView(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_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 protected boolean overScrollBy(int deltaX, int deltaY, int * scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int * maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { // This is * where the magic happens, we have replaced the incoming // maxOverScrollY * with our own custom variable mMaxYOverscrollDistance; * * return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, * scrollRangeY, maxOverScrollX, mMaxYOverscrollDistance, isTouchEvent); } */ }以下是pull_to_refresh_head.xml文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:clickable="true" android:focusable="false" android:orientation="vertical" > <RelativeLayout android:id="@+id/head_contentLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:paddingBottom="10dip" android:paddingTop="10dip" > <ImageView android:id="@+id/head_arrowImageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerVertical="true" android:layout_marginLeft="50dp" android:background="@drawable/ic_pulltorefresh_arrow" /> <FrameLayout android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_marginLeft="100dip" android:layout_marginRight="10dip" android:paddingBottom="10dip" android:paddingTop="10dip" > <ProgressBar android:id="@+id/head_progressBar" style="@style/loading_small" android:visibility="gone" /> </FrameLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center_horizontal" android:orientation="vertical" > <TextView android:id="@+id/head_tipsTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/pull_to_refresh_pull_label" android:textColor="@color/list_title" /> <TextView android:id="@+id/head_lastUpdatedTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/list_title" android:textSize="10sp" android:visibility="gone" /> </LinearLayout> </RelativeLayout> </LinearLayout>
package com.example.ui.widget; import com.example.androidtest.R; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.RelativeLayout; import android.widget.TextView; public class PullToRefreshFooter extends RelativeLayout { public final static int STATE_NORMAL = 0; //正常状态,还有很多其它须要载入 public final static int STATE_READY = 1; public final static int STATE_LOADING = 2; //正在载入中 public final static int STATE_LOADFULL = 3;//数据所有载入完 public final static int STATE_NULL = 4;//没有数据,初始状态 private Context mContext; private View mContentView; private View mProgressBar; private TextView mHintView; private int state; public PullToRefreshFooter(Context context) { super(context); initView(context); setVisibility(View.GONE); } public PullToRefreshFooter(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public void setState(int state) { this.state = state; // mHintView.setVisibility(View.INVISIBLE); // mProgressBar.setVisibility(View.INVISIBLE); // mHintView.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.GONE); mHintView.setVisibility(View.VISIBLE); setVisibility(View.VISIBLE); if (state == STATE_READY) { mHintView.setText(R.string.load_ready); } else if (state == STATE_LOADING) { mProgressBar.setVisibility(View.VISIBLE); mHintView.setText(R.string.load_ing); } else if (state == STATE_LOADFULL){ mHintView.setText(R.string.load_full); } else if (state == STATE_NULL) { mHintView.setText(R.string.load_empty); //setVisibility(View.GONE); } else { mHintView.setText(R.string.load_more); } } public int getState() { return state; } public void setBottomMargin(int height) { if (height < 0) return ; RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)mContentView.getLayoutParams(); lp.bottomMargin = height; mContentView.setLayoutParams(lp); } public int getBottomMargin() { RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)mContentView.getLayoutParams(); return lp.bottomMargin; } /** * normal status */ public void normal() { mHintView.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.GONE); } /** * loading status */ public void loading() { mHintView.setVisibility(View.GONE); mProgressBar.setVisibility(View.VISIBLE); } /** * hide footer when disable pull load more */ public void hide() { RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)mContentView.getLayoutParams(); lp.height = 0; mContentView.setLayoutParams(lp); } /** * show footer */ public void show() { RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)mContentView.getLayoutParams(); lp.height = android.view.ViewGroup.LayoutParams.WRAP_CONTENT; mContentView.setLayoutParams(lp); } private void initView(Context context) { mContext = context; RelativeLayout moreView = (RelativeLayout)LayoutInflater.from(mContext).inflate(R.layout.listview_footer, null); addView(moreView); moreView.setLayoutParams(new RelativeLayout.LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT)); mContentView = moreView.findViewById(R.id.listview_foot_content); mProgressBar = moreView.findViewById(R.id.listview_foot_progress); mHintView = (TextView)moreView.findViewById(R.id.listview_foot_more); } }
以下是listview_footer.xml文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/listview_foot_content" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="3dp" android:clickable="true" android:focusable="false"> <TextView android:id="@+id/listview_foot_more" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:padding="5dp" android:textColor="@color/list_subTitle" android:text="@string/load_empty"/> <ProgressBar android:id="@+id/listview_foot_progress" android:layout_toLeftOf="@id/listview_foot_more" android:layout_centerVertical="true" style="@style/loading_small"/> </RelativeLayout>
然后我们就能够使用这个刷新类的自己定义控件了。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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" android:orientation="vertical" tools:context=".PullRefreshActivity" > <com.example.ui.widget.PullToRefreshListView android:id="@+id/listview" style="@style/common_listview" android:layout_width="match_parent" android:divider="@drawable/divided_line" android:dividerHeight="1dp" android:listSelector="@drawable/listview_item_nocolor_selector" android:paddingLeft="8dp" android:paddingRight="8dp" android:scrollbars="none" > </com.example.ui.widget.PullToRefreshListView> </LinearLayout>
然后再Activity中载入这个控件:
package com.example.androidtest; import com.example.ui.adapter.PullRefreshAdapter; import com.example.ui.widget.PullToRefreshListView; import com.example.ui.widget.PullToRefreshListView.OnRefreshListener; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.widget.Toast; public class PullRefreshActivity extends Activity { private Context mContext; private PullRefreshAdapter pullRefreshAdapter; private PullToRefreshListView listView; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity_pulltorefresh); initView(); initListView(); } private void initView() { mContext = this; listView = (PullToRefreshListView) findViewById(R.id.listview); } private void initListView() { pullRefreshAdapter = new PullRefreshAdapter(mContext); listView.setLoadMoreAdapter(pullRefreshAdapter); listView.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { // TODO Auto-generated method stub Toast.makeText(mContext, "哈哈", Toast.LENGTH_SHORT).show(); } }); } }
package com.example.ui.adapter; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; public class PullRefreshAdapter extends BaseAdapter { private Context mContext; public PullRefreshAdapter(Context context) { super(); this.mContext = context; } @Override public int getCount() { // TODO Auto-generated method stub return 0; } @Override public Object getItem(int position) { // TODO Auto-generated method stub return null; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub return null; } }
得到的效果例如以下:
上面显示出来了载入刷新的功能,能够在须要刷新的时候显示这个控件。这样对UI显示地比較方面。
因为时间比較仓促,临时先放上源代码,以后再好好分析源代码。请大家多多不吝赐教。