Android RefreshLayout實(shí)現(xiàn)下拉刷新布局
項(xiàng)目中需要下拉刷新的功能,但是這個(gè)View不是ListView這類的控件,需要ViewGroup實(shí)現(xiàn)這個(gè)功能,一開始網(wǎng)上大略找了一下,沒發(fā)現(xiàn)特別合適的,代碼也是沒怎么看懂,所以決定還是自己寫一個(gè)。
于是翻出XlistView的源碼看是一點(diǎn)一點(diǎn)看,再大致理解了XLisview源碼,終于決定自己動(dòng)手啦
為了省事,headView還是用了XListView的HeadView,省了很多事:)
下拉刷新,下拉刷新,肯定是先實(shí)現(xiàn)下拉功能,最開始我是打算通過 extends ScrollView 來實(shí)現(xiàn),因?yàn)橛鞋F(xiàn)成的滾動(dòng)效果嘛,可是實(shí)際因?yàn)閮蓚€(gè)原因放棄了:
1、ScrollView下只能有一個(gè)子控件View ,雖然在 Scroll下添加一個(gè)ViewGroup,然后講headView動(dòng)態(tài)添加進(jìn)前面的ViewGroup,但是我還是比較習(xí)慣studio的可視化預(yù)覽,總覺得不直觀!
2、 ScrollView內(nèi)嵌ListView時(shí)會(huì)發(fā)生 沖突,還需要去重寫ListView。于是放棄換個(gè)思路!
關(guān)于上面的原因1:動(dòng)態(tài)添加headView進(jìn)ScrollView的中GroupView中,可以在重寫ScrollView的onViewAdded()方法,將初始化時(shí)解析的headView添加進(jìn)子GroupView
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
//因?yàn)閔eadView要在最上面,最先想到的就是Vertical的LinearLayout
LinearLayout linearLayout = (LinearLayout) getChildAt(0);
linearLayout.addView(view, 0);
}
換個(gè)思路,通過extends LinearLayout來實(shí)現(xiàn)吧!
先做準(zhǔn)備工作,我們需要一個(gè)HeaderView以及要獲取到HeaderView的高度,還有初始時(shí)Layout的高度
private void initView(Context context) {
mHeaderView = new SRefreshHeader(context);
mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content);
setOrientation(VERTICAL);
addView(mHeaderView, 0);
getHeaderViewHeight();
getViewHeight();
}
mHeaderView = new SRefreshHeader(context);
通過構(gòu)造方法實(shí)例化HeaderView
mHeaderViewContent = (RelativeLayout)
mHeaderView.findViewById(R.id.slistview_header_content);
這是解析headerView內(nèi)容區(qū)域iew,等會(huì)兒要獲取這個(gè)view的高度,你肯定會(huì)問為啥不用上面的mHeaderView來獲取高度,點(diǎn)進(jìn)構(gòu)造方法里可以看到如下代碼
// 初始情況,設(shè)置下拉刷新view高度為0 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null); w(mContainer, lp);
如果直接獲取mHeaderView的高度 那肯定是0
getHeaderViewHeight();
getViewHeight();
分別是獲取HeaderView的高度和Layout的初始高度
/**
* 獲取headView高度
*/
private void getHeaderViewHeight() {
ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver();
vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mHeaderViewHeight = mHeaderViewContent.getHeight();
mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
/**
* 獲取SRefreshLayout當(dāng)前實(shí)例的高度
*/
private void getViewHeight() {
ViewTreeObserver thisView = getViewTreeObserver();
thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight();
SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
準(zhǔn)備工作完成了,接下來就是要成下拉操作了
到這里,肯定一下就想到了onTouchEvent()方法,是的!現(xiàn)在就開始在這里施工
實(shí)現(xiàn)下拉一共 會(huì)經(jīng)歷三個(gè)過程
ACTION_UP→ACTION_MOVE→ACTION_UP
在ACTION_UP事件中,也就是手指按下的時(shí)候,我們需要做的只是記錄按下時(shí)候的坐標(biāo)
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//記錄起始高度
mLastY = ev.getRawY();//記錄按下時(shí)的Y坐標(biāo)
break;
然后就是ACTION_MOVE事件了,這里是最重要的,因?yàn)橄吕瓡r(shí)HeadView和Layout的高度變化都在這里進(jìn)行
case MotionEvent.ACTION_MOVE:
if (!isRefreashing)
isRefreashing = true;
final float deltaY = ev.getRawY() - mLastY;
mLastY = ev.getRawY();
updateHeaderViewHeight(deltaY / 1.8f);//按一定比例縮小移動(dòng)距離
updateHeight();
break;
里面的updateHeaderViewHeight和updateHeight分別是改變HeaderView的高度和Layout的高度
private void updateHeight() {
ViewGroup.LayoutParams lp = getLayoutParams();
//更新當(dāng)前l(fā)ayout實(shí)例高度為headerView高度加上最初的layout高度
//如果不更新layout 會(huì)造成內(nèi)容高度壓縮 無法保持比例
lp.height = (mHeight + mHeaderView.getVisiableHeight());
setLayoutParams(lp);
}
private void updateHeaderViewHeight(float space) {
// if (space < 0)
// space = 0;
// int factHeight = (int) (space - mHeaderViewHeight);
if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) {
//如果不處于刷新中同時(shí)如果高度
if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) {
mHeaderView.setState(SRefreshHeader.STATE_NORMAL);
}
if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) {
mHeaderView.setState(SRefreshHeader.STATE_READY);
}
}
mHeaderView.setVisiableHeight((int) space
+ mHeaderView.getVisiableHeight());
}
更新Header高度時(shí),通過下拉的距離來判斷是否到達(dá)刷新的距離,上面代碼中我設(shè)定的是到達(dá)mHeaderView初始高度的兩倍,就進(jìn)入“釋放刷新”的狀態(tài),如果沒有達(dá)到則保持“下拉刷新”的狀態(tài)
HeaderView中的狀態(tài)一共設(shè)定了3個(gè)分別是
public final static int STATE_NORMAL = 0;//下拉刷新 public final static int STATE_READY = 1;//釋放刷新 public final static int STATE_REFRESHING = 2;//刷新中
更新高度的方法headerView和layout都是相同的,就是原高度加上移動(dòng)的距離重新賦給headerView或者layout
mHeaderView.setVisiableHeight((int) space
+ mHeaderView.getVisiableHeight());
最后就是ACTION_UP事件了就是手指離開屏幕的時(shí)候,在這里我們需要根據(jù)headerView目前狀態(tài)來決定headerView的最終狀態(tài)!
case MotionEvent.ACTION_UP:
//松開時(shí)
//避免點(diǎn)擊事件觸發(fā)
if (!isRefreashing)
break;
//如果headView狀態(tài)處于READY狀態(tài) 則說明松開時(shí)應(yīng)該進(jìn)入REFRESHING狀態(tài)
if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {
mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);
}
//根據(jù)狀態(tài)重置SrefreshLayout當(dāng)前實(shí)例和headView高度
resetHeadView(mHeaderView.getStatus());
reset(mHeaderView.getStatus());
mLastY = -1;//重置坐標(biāo)
break;
resetHeadView和reset分別是重置headerView高度和layout高度的方法
private void reset(int status) {
ViewGroup.LayoutParams lp = getLayoutParams();
switch (status) {
case SRefreshHeader.STATE_REFRESHING:
lp.height = mHeight + mHeaderViewHeight;
break;
case SRefreshHeader.STATE_NORMAL:
lp.height = mHeight;
break;
}
setLayoutParams(lp);
}
private void resetHeadView(int status) {
switch (status) {
case SRefreshHeader.STATE_REFRESHING:
mHeaderView.setVisiableHeight(mHeaderViewHeight);
break;
case SRefreshHeader.STATE_NORMAL:
mHeaderView.setVisiableHeight(0);
break;
}
}
實(shí)現(xiàn)方式也是一樣的。根據(jù)狀態(tài)來判斷,如果是處于刷新中,那headerView應(yīng)該正常顯示,并且高度是初始的高度,如果處于NORMAL,也就是"下拉刷新"狀態(tài),那么說未觸發(fā)刷新,重置時(shí),headerView應(yīng)該被隱藏,也就是高度重置為0
到這里下拉刷新操作也基本完成了,還需要加一個(gè)回調(diào)接口進(jìn)行通知
interface OnRefreshListener {
void onRefresh();
}
case MotionEvent.ACTION_UP:
//松開時(shí)
//避免點(diǎn)擊事件觸發(fā)
if (!isRefreashing)
break;
//如果headView狀態(tài)處于READY狀態(tài) 則說明松開時(shí)應(yīng)該進(jìn)入REFRESHING狀態(tài)
if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {
mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);
if (mOnRefreshListener != null)
mOnRefreshListener.onRefresh();
}
//根據(jù)狀態(tài)重置SrefreshLayout當(dāng)前實(shí)例和headView高度
resetHeadView(mHeaderView.getStatus());
reset(mHeaderView.getStatus());
mLastY = -1;//重置坐標(biāo)
break;
好,到這里就基本完成了,試試效果吧。咦,發(fā)現(xiàn)一個(gè)問題,嵌套ListView的時(shí)候?yàn)槭裁催@個(gè)Layout不能執(zhí)行下拉刷新!仔細(xì)想想應(yīng)該是事件分發(fā)的問題,還需要處理一下事件的攔截!
關(guān)于事件攔截的處理,閱讀了鴻洋大神寫的viewgroup事件分發(fā)的博客和Android-Ultra-Pull-To-Refresh的部分源碼,從中找到了解決辦法:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
AbsListView absListView = null;
for (int n = 0; n < getChildCount(); n++) {
if (getChildAt(n) instanceof AbsListView) {
absListView = (ListView) getChildAt(n);
Logs.v("查找到listView");
}
}
if (absListView == null)
return super.onInterceptTouchEvent(ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float space = ev.getRawY() - mStartY;
Logs.v("space:" + space);
if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) {
Logs.v("攔截成功");
return true;
} else {
Logs.v("不攔截");
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
其中
if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0)
space即移動(dòng)的距離 canScrollVertically()是判斷ListView能否在垂直方向上滾動(dòng),參數(shù)為負(fù)數(shù)時(shí)代表向上,為正數(shù)時(shí)代碼向下滾動(dòng),最后一個(gè)就是ListView第一個(gè)可見的item的postion
加上上面的事件攔截處理,一個(gè)可以滿足開頭提到的需求的Viewgroup也就完成了!

下面貼上Layout的源碼和HeaderView(直接使用的XlistView的HeaderView)的源碼
public class SRefreshLayout extends LinearLayout {
private SRefreshHeader mHeaderView;
private RelativeLayout mHeaderViewContent;
private boolean isRefreashing;
private float mLastY = -1;//按下的起始高度
private int mHeaderViewHeight;//headerView內(nèi)容高度
private int mHeight;//布局高度
private float mStartY;
interface OnRefreshListener {
void onRefresh();
}
public OnRefreshListener mOnRefreshListener;
public SRefreshLayout(Context context) {
super(context);
initView(context);
}
public SRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public SRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
mHeaderView = new SRefreshHeader(context);
mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content);
setOrientation(VERTICAL);
addView(mHeaderView, 0);
getHeaderViewHeight();
getViewHeight();
}
/**
* 獲取headView高度
*/
private void getHeaderViewHeight() {
ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver();
vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mHeaderViewHeight = mHeaderViewContent.getHeight();
mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
/**
* 獲取SRefreshLayout當(dāng)前實(shí)例的高度
*/
private void getViewHeight() {
ViewTreeObserver thisView = getViewTreeObserver();
thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight();
SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
AbsListView absListView = null;
for (int n = 0; n < getChildCount(); n++) {
if (getChildAt(n) instanceof AbsListView) {
absListView = (ListView) getChildAt(n);
Logs.v("查找到listView");
}
}
if (absListView == null)
return super.onInterceptTouchEvent(ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float space = ev.getRawY() - mStartY;
Logs.v("space:" + space);
if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) {
Logs.v("攔截成功");
return true;
} else {
Logs.v("不攔截");
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mLastY == -1)
mLastY = ev.getRawY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//記錄起始高度
mLastY = ev.getRawY();//記錄按下時(shí)的Y坐標(biāo)
break;
//手指離開屏幕時(shí)
case MotionEvent.ACTION_UP:
//松開時(shí)
//避免點(diǎn)擊事件觸發(fā)
if (!isRefreashing)
break;
//如果headView狀態(tài)處于READY狀態(tài) 則說明松開時(shí)應(yīng)該進(jìn)入REFRESHING狀態(tài)
if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {
mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);
if (mOnRefreshListener != null)
mOnRefreshListener.onRefresh();
}
//根據(jù)狀態(tài)重置SrefreshLayout當(dāng)前實(shí)例和headView高度
resetHeadView(mHeaderView.getStatus());
reset(mHeaderView.getStatus());
mLastY = -1;//重置坐標(biāo)
break;
case MotionEvent.ACTION_MOVE:
if (!isRefreashing)
isRefreashing = true;
final float deltaY = ev.getRawY() - mLastY;
mLastY = ev.getRawY();
updateHeaderViewHeight(deltaY / 1.8f);//按一定比例縮小移動(dòng)距離
updateHeight();
break;
}
return super.onTouchEvent(ev);
}
private void reset(int status) {
ViewGroup.LayoutParams lp = getLayoutParams();
switch (status) {
case SRefreshHeader.STATE_REFRESHING:
lp.height = mHeight + mHeaderViewHeight;
break;
case SRefreshHeader.STATE_NORMAL:
lp.height = mHeight;
break;
}
setLayoutParams(lp);
}
private void resetHeadView(int status) {
switch (status) {
case SRefreshHeader.STATE_REFRESHING:
mHeaderView.setVisiableHeight(mHeaderViewHeight);
break;
case SRefreshHeader.STATE_NORMAL:
mHeaderView.setVisiableHeight(0);
break;
}
}
private void updateHeight() {
ViewGroup.LayoutParams lp = getLayoutParams();
//更新當(dāng)前l(fā)ayout實(shí)例高度為headerView高度加上最初的layout高度
//如果不更新layout 會(huì)造成內(nèi)容高度壓縮 無法保持比例
lp.height = (mHeight + mHeaderView.getVisiableHeight());
setLayoutParams(lp);
}
private void updateHeaderViewHeight(float space) {
// if (space < 0)
// space = 0;
// int factHeight = (int) (space - mHeaderViewHeight);
if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) {
//如果不處于刷新中同時(shí)如果高度
if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) {
mHeaderView.setState(SRefreshHeader.STATE_NORMAL);
}
if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) {
mHeaderView.setState(SRefreshHeader.STATE_READY);
}
}
mHeaderView.setVisiableHeight((int) space
+ mHeaderView.getVisiableHeight());
}
public void stopRefresh() {
if (mHeaderView.getStatus() == SRefreshHeader.STATE_REFRESHING) {
mHeaderView.setState(SRefreshHeader.STATE_NORMAL);
resetHeadView(SRefreshHeader.STATE_NORMAL);
reset(SRefreshHeader.STATE_NORMAL);
}
}
public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
this.mOnRefreshListener = onRefreshListener;
}
}
public class SRefreshHeader extends LinearLayout {
private LinearLayout mContainer;
private int mState = STATE_NORMAL;
private Animation mRotateUpAnim;
private Animation mRotateDownAnim;
private final int ROTATE_ANIM_DURATION = 500;
public final static int STATE_NORMAL = 0;//下拉刷新
public final static int STATE_READY = 1;//釋放刷新
public final static int STATE_REFRESHING = 2;//刷新中
private ImageView mHeadArrowImage;
private TextView mHeadLastRefreashTimeTxt;
private TextView mHeadHintTxt;
private TextView mHeadLastRefreashTxt;
private ProgressBar mRefreshingProgress;
public SRefreshHeader(Context context) {
super(context);
initView(context);
}
/**
* @param context
* @param attrs
*/
public SRefreshHeader(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
private void initView(Context context) {
// 初始情況,設(shè)置下拉刷新view高度為0
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null);
addView(mContainer, lp);
setGravity(Gravity.BOTTOM);
mHeadArrowImage = (ImageView) findViewById(R.id.slistview_header_arrow);
mHeadLastRefreashTimeTxt = (TextView) findViewById(R.id.slistview_header_time);
mHeadHintTxt = (TextView) findViewById(R.id.slistview_header_hint_text);
mHeadLastRefreashTxt = (TextView) findViewById(R.id.slistview_header_last_refreash_txt);
mRefreshingProgress = (ProgressBar) findViewById(R.id.slistview_header_progressbar);
mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);
mRotateUpAnim.setFillAfter(true);
mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
mRotateDownAnim.setFillAfter(true);
}
public void setState(int state) {
if (state == mState) return;
if (state == STATE_REFRESHING) { // 顯示進(jìn)度
mHeadArrowImage.clearAnimation();
mHeadArrowImage.setVisibility(View.INVISIBLE);
mRefreshingProgress.setVisibility(View.VISIBLE);
} else { // 顯示箭頭圖片
mHeadArrowImage.setVisibility(View.VISIBLE);
mRefreshingProgress.setVisibility(View.INVISIBLE);
}
switch (state) {
case STATE_NORMAL:
if (mState == STATE_READY) {
mHeadArrowImage.startAnimation(mRotateDownAnim);
}
if (mState == STATE_REFRESHING) {
mHeadArrowImage.clearAnimation();
}
mHeadHintTxt.setText("下拉刷新");
break;
case STATE_READY:
if (mState != STATE_READY) {
mHeadArrowImage.clearAnimation();
mHeadArrowImage.startAnimation(mRotateUpAnim);
mHeadHintTxt.setText("松開刷新");
}
break;
case STATE_REFRESHING:
mHeadHintTxt.setText("正在刷新");
break;
default:
}
mState = state;
}
public void setVisiableHeight(int height) {
if (height < 0)
height = 0;
LayoutParams lp = (LayoutParams) mContainer
.getLayoutParams();
lp.height = height;
mContainer.setLayoutParams(lp);
}
public int getStatus() {
return mState;
}
public int getVisiableHeight() {
return mContainer.getHeight();
}
}
最后是布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom">
<RelativeLayout
android:id="@+id/slistview_header_content"
android:layout_width="match_parent"
android:layout_height="60dp">
<LinearLayout
android:id="@+id/slistview_header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/slistview_header_hint_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉刷新" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp">
<TextView
android:id="@+id/slistview_header_last_refreash_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="上次刷新時(shí)間"
android:textSize="12sp" />
<TextView
android:id="@+id/slistview_header_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<ProgressBar
android:id="@+id/slistview_header_progressbar"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/slistview_header_text"
android:visibility="invisible" />
<ImageView
android:id="@+id/slistview_header_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/slistview_header_progressbar"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/slistview_header_text"
android:src="@drawable/mmtlistview_arrow" />
</RelativeLayout>
</LinearLayout>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 探究Android中ListView復(fù)用導(dǎo)致布局錯(cuò)亂的解決方案
- Android ListView自動(dòng)顯示隱藏布局的實(shí)現(xiàn)方法
- Android 五大布局方式詳解
- Android RecyclerView加載不同布局簡(jiǎn)單實(shí)現(xiàn)
- Android實(shí)現(xiàn)氣泡布局/彈窗效果 氣泡尖角方向及偏移量可控
- Android動(dòng)畫效果之自定義ViewGroup添加布局動(dòng)畫(五)
- Android實(shí)現(xiàn)的ListView分組布局改進(jìn)示例
- Android布局實(shí)現(xiàn)圓角邊框效果
- Android 動(dòng)態(tài)改變布局實(shí)例詳解
相關(guān)文章
Android開發(fā)中優(yōu)秀的app 異常處理機(jī)制
這篇文章主要介紹了Android開發(fā)中優(yōu)秀的app 異常處理機(jī)制 的相關(guān)資料,需要的朋友可以參考下2015-12-12
Android來電監(jiān)聽和去電監(jiān)聽實(shí)現(xiàn)代碼
本文是關(guān)于來點(diǎn)監(jiān)聽和去電監(jiān)聽展開問題,通過實(shí)例代碼講解,對(duì)android來電監(jiān)聽和去電監(jiān)聽的相關(guān)知識(shí)感興趣的朋友一起看看吧2017-06-06
Android實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)攔截讀取功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)攔截讀取功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08
Java程序員轉(zhuǎn)Android開發(fā)必讀經(jīng)驗(yàn)一份
小編最近幾日偷偷的發(fā)現(xiàn)部分Java程序員想轉(zhuǎn)安卓開發(fā),故此加緊補(bǔ)充知識(shí),為大家搜集資料,積極整理前人的經(jīng)驗(yàn),希望可以給正處于困惑中的你,帶來些許的幫助。2017-11-11
Android 利用反射+try catch實(shí)現(xiàn)sdk按需引入依賴庫(kù)的方法
這篇文章主要介紹了Android 利用反射+try catch來實(shí)現(xiàn)sdk按需引入依賴庫(kù),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
Android 靜默方式實(shí)現(xiàn)批量安裝卸載應(yīng)用程序的深入分析
本篇文章是對(duì)Android 靜默方式實(shí)現(xiàn)批量安裝卸載應(yīng)用程序進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
android listview 水平滾動(dòng)和垂直滾動(dòng)的小例子
android listview 水平滾動(dòng)和垂直滾動(dòng)的小例子,需要的朋友可以參考一下2013-05-05
Android中獲取sha1證書指紋數(shù)據(jù)的方法
大家都知道在Android開發(fā)中,經(jīng)常要獲取sha1證書指紋,所以這篇文章主要介紹在Android中如何使用命令獲取sha1證書指紋數(shù)據(jù)的方法,有需要的可以參考借鑒。2016-09-09
Android中通過樣式來去除app的頭及界面全屏(備忘)的實(shí)現(xiàn)方法
這篇文章主要介紹了Android中通過樣式來去除app的頭及界面全屏(備忘)的相關(guān)資料,需要的朋友可以參考下2016-12-12

