欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android開發(fā)之無痕過渡下拉刷新控件的實現(xiàn)思路詳解

 更新時間:2016年11月22日 09:18:40   作者:一個忠實的安卓腦殘粉  
下拉刷新效果功能在程序開發(fā)中經(jīng)常會見到,今天小編抽時間給大家分享Android開發(fā)之無痕過渡下拉刷新控件的實現(xiàn)思路詳解,需要的朋友參考下吧

相信大家已經(jīng)對下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳瑯滿目,然而有很多在我看來略有缺陷,接下來我將說明一下存在的缺陷問題,然后提供一種思路來解決這一缺陷,廢話不多說!往下看嘞!

1.市面一些下拉刷新控件普遍缺陷演示

以直播吧APP為例:

第1種情況:

滑動控件在初始的0位置時,手勢往下滑動然后再往上滑動,可以看到滑動到初始位置時滑動控件不能滑動。

原因:

下拉刷新控件響應(yīng)了觸摸事件,后續(xù)的一系列事件都由它來處理,當(dāng)滑動控件到頂端的時候,滑動事件都被下拉刷新控件消費掉了,傳遞不到它的子控件即滑動控件,因此滑動控件不能滑動。

這里寫圖片描述 

第2種情況:

滑動控件滑動到某個非0位置時,這時下拉回0位置時,可以看到下拉刷新頭部沒有被拉出來。 

原因:

滑動控件響應(yīng)了觸摸事件,后續(xù)的一系列事件都由它來處理,當(dāng)滑動控件到頂端的時候,滑動事件都被滑動控件消費掉了,父控件即下拉刷新控件消費不了滑動事件,因此下拉刷新頭部沒有被拉出來。

這里寫圖片描述

可能大部分人覺得無關(guān)痛癢,把手指抬起再下拉就可以了,but對于強迫癥的我而言,能提供一個無痕過渡才是最符合操作邏輯的,因此接下來我來講解下實現(xiàn)的思路。

2.實現(xiàn)的思路講解

2.1.事件分發(fā)機制簡介(來源于Android開發(fā)藝術(shù)探索)

dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法的關(guān)系偽代碼

public boolean dispatchTouchEvent(MotionEvent ev) { 
boolean consume = false;
if(onInterceptTouchEvent(ev)) { 
consume = onTouchEvent(ev);
} else { 
consume = child.dispatchTouchEvent(ev); 
}
return consume; 
}

1.由代碼可知若當(dāng)前View攔截事件,就交給自己的onTouchEvent去處理,否則就丟給子View繼續(xù)走相同的流程。

2.事件傳遞順序:Activity -> Window -> View,如果View都不處理,最終將由Activity的onTouchEvent
處理,是一種責(zé)任鏈模式的實現(xiàn)。

3.正常情況,一個事件序列只能被一個View攔截且消耗。

4.某個View一旦決定攔截,這一個事件序列只能由它處理,并且它的onInterceptTouchEvent不會再被調(diào)用

5.不消耗ACTION_DOWN,則事件序列都會由其父元素處理。

2.2.一般下拉刷新的實現(xiàn)思路猜想

首先,下拉刷新控件作為一個容器,需要重寫onInterceptTouchEvent和onTouchEvent這兩個方法,然后在onInterceptTouchEvent中判斷ACTION_DOWN事件,根據(jù)子控件的滑動距離做出判斷,若還沒滑動過,則onInterceptTouchEvent返回true表示其攔截事件,然后在onTouchEvent中進行下拉刷新的頭部顯示隱藏的邏輯處理;若子控件滑動過了,不攔截事件,onInterceptTouchEvent返回false,后續(xù)其下拉刷新的頭部顯示隱藏的邏輯處理就無法被調(diào)用了。

2.3.無痕過渡下拉刷新控件的實現(xiàn)思路

從2.2中可以看出,要想無痕過渡,下拉刷新控件不能攔截事件,這時候你可能會問,既然把事件給了子控件,后續(xù)拉刷新頭部邏輯怎么實現(xiàn)呢?

這時候就要用到一般都忽略的事件分發(fā)方法dispatchTouchEvent了,此方法在ViewGroup默認(rèn)返回true表示分發(fā)事件,即使子控件攔截了事件,父布局的dispatchTouchEvent仍然會被調(diào)用,因為事件是傳遞下來的,這個方法必定被調(diào)用。

所以我們可以在dispatchTouchEvent時對子控件的滑動距離做出判斷,在這里把下拉刷新的頭部的邏輯處理掉,同時在函數(shù)調(diào)用return super.dispatchTouchEvent(event) 前把event的action設(shè)置為ACTION_CANCEL,這樣子子控件就不會響應(yīng)滑動的操作。

3.代碼實現(xiàn)

3.1.確定需求

需要適配任意控件,例如RecyclerView、ListView、ViewPager、WebView以及普通的不能滑動的View

不能影響子控件原來的事件邏輯

暴露方法提供手動調(diào)用刷新功能

可以設(shè)置禁止下拉刷新功能

3.2.代碼講解

需要的變量

public class RefreshLayout extends LinearLayout {
// 隱藏的狀態(tài)
private static final int HIDE = 0;
// 下拉刷新的狀態(tài)
private static final int PULL_TO_REFRESH = 1;
// 松開刷新的狀態(tài)
private static final int RELEASE_TO_REFRESH = 2;
// 正在刷新的狀態(tài)
private static final int REFRESHING = 3;
// 正在隱藏的狀態(tài)
private static final int HIDING = 4;
// 當(dāng)前狀態(tài)
private int mCurrentState = HIDE;
// 頭部動畫的默認(rèn)時間(單位:毫秒)
public static final int DEFAULT_DURATION = 200;
// 頭部高度
private int mHeaderHeight;
// 內(nèi)容控件的滑動距離
private int mContentViewOffset;
// 記錄上次的Y坐標(biāo)
private int mLastY;
// 最小滑動響應(yīng)距離
private int mScaledTouchSlop;
// 滑動的偏移量
private int mTotalDeltaY;
// 是否在處理頭部
private boolean mIsHeaderHandling;
// 是否可以下拉刷新
private boolean mIsRefreshable = true;
// 內(nèi)容控件是否可以滑動,不能滑動的控件會做觸摸事件的優(yōu)化
private boolean mContentViewScrollable = true;
// 頭部,為了方便演示選取了TextView
private TextView mHeader;
// 容器要承載的內(nèi)容控件,在XML里面要放置好
private View mContentView;
// 值動畫,由于頭部顯示隱藏
private ValueAnimator mHeaderAnimator;
// 刷新的監(jiān)聽器
private OnRefreshListener mOnRefreshListener;

初始化時創(chuàng)建頭部執(zhí)行顯示隱藏的值動畫,添加頭部到布局中,并且通過設(shè)置paddingTop隱藏頭部

public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
addHeader(context);
}
private void init() {
mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
mHeaderAnimator = ValueAnimator.ofInt(0).setDuration(DEFAULT_DURATION);
mHeaderAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if (getContext() == null) {
// 若是退出Activity了,動畫結(jié)束不必執(zhí)行頭部動作
return;
}
// 通過設(shè)置paddingTop實現(xiàn)顯示或者隱藏頭部
int offset = (Integer) valueAnimator.getAnimatedValue();
mHeader.setPadding(0, offset, 0, 0);
}
});
mHeaderAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (getContext() == null) {
// 若是退出Activity了,動畫結(jié)束不必執(zhí)行頭部動作
return;
}
if (mCurrentState == RELEASE_TO_REFRESH) {
// 釋放刷新狀態(tài)執(zhí)行的動畫結(jié)束,意味接下來就是刷新了,改狀態(tài)并且調(diào)用刷新的監(jiān)聽
mHeader.setText("正在刷新...");
mCurrentState = REFRESHING;
if (mOnRefreshListener != null) {
mOnRefreshListener.onRefresh();
}
} else if (mCurrentState == HIDING) {
// 下拉狀態(tài)執(zhí)行的動畫結(jié)束,隱藏頭部,改狀態(tài)
mHeader.setText("我是頭部");
mCurrentState = HIDE;
}
}
});
}
// 頭部的創(chuàng)建
private void addHeader(Context context) {
// 強制垂直方法
setOrientation(LinearLayout.VERTICAL);
mHeader = new TextView(context);
mHeader.setBackgroundColor(Color.GRAY);
mHeader.setTextColor(Color.WHITE);
mHeader.setText("我是頭部");
mHeader.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
mHeader.setGravity(Gravity.CENTER);
addView(mHeader, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
mHeader.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 算出頭部高度
mHeaderHeight = mHeader.getMeasuredHeight();
// 移除監(jiān)聽
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mHeader.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
mHeader.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
// 設(shè)置paddingTop為-mHeaderHeight,剛好把頭部隱藏掉了
mHeader.setPadding(0, -mHeaderHeight, 0, 0);
}
});
}

在填充完布局后取出內(nèi)容控件

@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 設(shè)置長點擊或者短點擊都能消耗事件,要不這樣做,若孩子都不消耗,最終點擊事件會被它的上級消耗掉,后面一系列的事件都只給它的上級處理了
setLongClickable(true);
// 獲取內(nèi)容控件
mContentView = getChildAt(1);
if (mContentView == null) {
// 為空拋異常,強制要求在XML設(shè)置內(nèi)容控件
throw new IllegalArgumentException("You must add a content view!");
}
if (!(mContentView instanceof ScrollingView 
|| mContentView instanceof WebView 
|| mContentView instanceof ScrollView 
|| mContentView instanceof AbsListView)) {
// 不是具有滾動的控件,這里設(shè)置標(biāo)志位
mContentViewScrollable = false;
}
}

重頭戲來了,分發(fā)對于下拉刷新的特殊處理:

1.mContentViewOffset用于判別內(nèi)容頁的滑動距離,在無偏移值時才去處理下拉刷新的操作;

2.在mContentViewOffset!=0即內(nèi)容頁滑動的第一個瞬間,強制把MOVE事件改為DOWN,是因為之前MOVE都被攔截掉了,若不給個DOWN讓內(nèi)容頁重新定下滑動起點,會有一瞬間滑動一大段距離的坑爹效果。

@Override
public boolean dispatchTouchEvent(final MotionEvent event) {
if (!mIsRefreshable) {
// 禁止下拉刷新,直接把事件分發(fā)
return super.dispatchTouchEvent(event);
}
if ((mCurrentState == REFRESHING 
|| mCurrentState == RELEASE_TO_REFRESH 
|| mCurrentState == HIDING) 
&& mHeaderAnimator.isRunning()) {
// 正在刷新,正在釋放,正在隱藏頭部都不處理事件,并且不分發(fā)下去
return true;
}
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE: {
int deltaY = y - mLastY;
if (mContentViewOffset == 0 && (deltaY > 0 || (deltaY < 0 && isHeaderShowing()))) {
// 偏移值為0時,下拉或者在頭部還在顯示的時候上滑時,交由自己處理滑動事件
mTotalDeltaY += deltaY;
if (mTotalDeltaY > 0 
&& mTotalDeltaY <= mScaledTouchSlop
&& !isHeaderShowing()) {
// 優(yōu)化下拉頭部,不要稍微一點位移就響應(yīng)
mLastY = y;
return super.dispatchTouchEvent(event);
}
// 處理事件
onHandleTouchEvent(event);
// 正在處理事件
mIsHeaderHandling = true;
if (mCurrentState == REFRESHING) {
// 正在刷新,不讓contentView響應(yīng)滑動
event.setAction(MotionEvent.ACTION_CANCEL);
}
} else if (mIsHeaderHandling) {
// 在頭部隱藏的那一瞬間的事件特殊處理
if (mContentViewScrollable) {
// 1.可滑動的View,由于之前處理頭部,之前的MOVE事件沒有傳遞到內(nèi)容頁,這里
// 需要要ACTION_DOWN來重新告知滑動的起點,不然會瞬間滑動一段距離
// 2.對于不滑動的View設(shè)置了點擊事件,若這里給它一個ACTION_DOWN事件,在手指
// 抬起時ACTION_UP事件會觸發(fā)點擊,因此這里做了處理
event.setAction(MotionEvent.ACTION_DOWN);
}
mIsHeaderHandling = false;
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
if (mContentViewOffset == 0 && isHeaderShowing()) {
// 處理手指抬起或取消事件
onHandleTouchEvent(event);
}
mTotalDeltaY = 0;
break;
}
default:
break;
}
mLastY = y;
if (mCurrentState != REFRESHING 
&& isHeaderShowing() 
&& event.getAction() != MotionEvent.ACTION_UP) {
// 不是在刷新的時候,并且頭部在顯示, 不讓contentView響應(yīng)事件
event.setAction(MotionEvent.ACTION_CANCEL);
}
return super.dispatchTouchEvent(event);
}

處理事件的邏輯:拿到下拉偏移量,然后動態(tài)去設(shè)置頭部的paddingTop值,即可實現(xiàn)顯示隱藏;手指抬起時根據(jù)狀態(tài)決定是顯示刷新還是直接隱藏頭部

// 自己處理事件
public boolean onHandleTouchEvent(MotionEvent event) {
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: {
// 拿到Y(jié)方向位移
int deltaY = y - mLastY;
// 除以3相當(dāng)于阻尼值
deltaY /= 3;
// 計算出移動后的頭部位置
int top = deltaY + mHeader.getPaddingTop();
// 控制頭部位置最大不超過-mHeaderHeight
if (top < -mHeaderHeight) {
mHeader.setPadding(0, -mHeaderHeight, 0, 0);
} else {
mHeader.setPadding(0, top, 0, 0);
}
if (mCurrentState == REFRESHING) {
// 之前還在刷新狀態(tài),繼續(xù)維持刷新狀態(tài)
mHeader.setText("正在刷新...");
break;
}
if (mHeader.getPaddingTop() > mHeaderHeight / 2) {
// 大于mHeaderHeight / 2時可以刷新了
mHeader.setText("可以釋放刷新...");
mCurrentState = RELEASE_TO_REFRESH;
} else {
// 下拉狀態(tài)
mHeader.setText("正在下拉...");
mCurrentState = PULL_TO_REFRESH;
}
break;
}
case MotionEvent.ACTION_UP: {
if (mCurrentState == RELEASE_TO_REFRESH) {
// 釋放刷新狀態(tài),手指抬起,通過動畫實現(xiàn)頭部回到(0,0)位置
mHeaderAnimator.setIntValues(mHeader.getPaddingTop(), 0);
mHeaderAnimator.setDuration(DEFAULT_DURATION);
mHeaderAnimator.start();
mHeader.setText("正在釋放...");
} else if (mCurrentState == PULL_TO_REFRESH || mCurrentState == REFRESHING) {
// 下拉狀態(tài)或者正在刷新狀態(tài),通過動畫隱藏頭部
mHeaderAnimator.setIntValues(mHeader.getPaddingTop(), -mHeaderHeight);
if (mHeader.getPaddingTop() <= 0) {
mHeaderAnimator.setDuration((long) (DEFAULT_DURATION * 1.0 / 
mHeaderHeight * (mHeader.getPaddingTop() + mHeaderHeight)));
} else {
mHeaderAnimator.setDuration(DEFAULT_DURATION);
}
mHeaderAnimator.start();
if (mCurrentState == PULL_TO_REFRESH) {
// 下拉狀態(tài)的話,把狀態(tài)改為正在隱藏頭部狀態(tài)
mCurrentState = HIDING;
mHeader.setText("收回頭部...");
}
}
break;
}
default:
break;
}
mLastY = y;
return super.onTouchEvent(event);
}

你可能會問了,這個mContentViewOffset怎么知道呢?接下來就是處理的方法,我會針對不同的滑動控件,去設(shè)置它們的滑動距離的監(jiān)聽,方法各種各樣,通過handleTargetOffset去判別View的類型采取不同的策略;然后你可能會覺得要是我那個控件我也要實現(xiàn)監(jiān)聽咋辦?這個簡單,繼承我已經(jīng)實現(xiàn)的監(jiān)聽器,再補充你想要的功能即可,這個時候就不能再調(diào)handleTargetOffset這個方法了唄。

// 設(shè)置內(nèi)容頁滑動距離
public void setContentViewOffset(int offset) {
mContentViewOffset = offset;
}
/**
* 根據(jù)不同類型的View采取不同類型策略去計算滑動距離
*
* @param view 內(nèi)容View
*/
public void handleTargetOffset(View view) {
if (view instanceof RecyclerView) {
((RecyclerView) view).addOnScrollListener(new RecyclerViewOnScrollListener());
} else if (view instanceof NestedScrollView) {
((NestedScrollView) view).setOnScrollChangeListener(new NestedScrollViewOnScrollChangeListener());
} else if (view instanceof WebView) {
view.setOnTouchListener(new WebViewOnTouchListener());
} else if (view instanceof ScrollView) {
view.setOnTouchListener(new ScrollViewOnTouchListener());
} else if (view instanceof ListView) {
((ListView) view).setOnScrollListener(new ListViewOnScrollListener());
}
}
/**
* 適用于RecyclerView的滑動距離監(jiān)聽
*/
public class RecyclerViewOnScrollListener extends RecyclerView.OnScrollListener {
int offset = 0;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
offset += dy;
setContentViewOffset(offset);
}
}
/**
* 適用于NestedScrollView的滑動距離監(jiān)聽
*/
public class NestedScrollViewOnScrollChangeListener implements NestedScrollView.OnScrollChangeListener {
@Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
setContentViewOffset(scrollY);
}
}
/**
* 適用于WebView的滑動距離監(jiān)聽
*/
public class WebViewOnTouchListener implements View.OnTouchListener {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
setContentViewOffset(view.getScrollY());
return false;
}
}
/**
* 適用于ScrollView的滑動距離監(jiān)聽
*/
public class ScrollViewOnTouchListener extends WebViewOnTouchListener {
}
/**
* 適用于ListView的滑動距離監(jiān)聽
*/
public class ListViewOnScrollListener implements AbsListView.OnScrollListener {
@Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem == 0) {
View c = view.getChildAt(0);
if (c == null) {
return;
}
int firstVisiblePosition = view.getFirstVisiblePosition();
int top = c.getTop();
int scrolledY = -top + firstVisiblePosition * c.getHeight();
setContentViewOffset(scrolledY);
} else {
setContentViewOffset(1);
}
}
}

最后參考谷歌大大的SwipeRefreshLayout提供setRefreshing來開啟或關(guān)閉刷新動畫,至于openHeader為啥要post(Runnable)呢?相信用過SwipeRefreshLayout在onCreate的時候直接調(diào)用setRefreshing(true)沒有小圓圈出來的都知道這個坑!

public void setRefreshing(boolean refreshing) {
if (refreshing && mCurrentState != REFRESHING) {
// 強開刷新頭部
openHeader();
} else if (!refreshing) {
closeHeader();
}
}
private void openHeader() {
post(new Runnable() {
@Override
public void run() {
mCurrentState = RELEASE_TO_REFRESH;
mHeaderAnimator.setDuration((long) (DEFAULT_DURATION * 2.5));
mHeaderAnimator.setIntValues(mHeader.getPaddingTop(), 0);
mHeaderAnimator.start();
}
});
}
private void closeHeader() {
mHeader.setText("刷新完畢,收回頭部...");
mCurrentState = HIDING;
mHeaderAnimator.setIntValues(mHeader.getPaddingTop(), -mHeaderHeight);
// 0~-mHeaderHeight用時DEFAULT_DURATION
mHeaderAnimator.setDuration(DEFAULT_DURATION);
mHeaderAnimator.start();
}

3.3.效果展示

這里寫圖片描述

這里寫圖片描述

這里寫圖片描述

除了以上三個還有在Demo中實現(xiàn)了ListView、ViewPager、ScrollView、NestedScrollView,具體看代碼即可

Demo地址:Github:RefreshLayoutDemo,覺得還不錯的話給個Star哦。

以上所述是小編給大家介紹的Android開發(fā)之無痕過渡下拉刷新控件的實現(xiàn)思路詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!

相關(guān)文章

  • Android自定義recyclerView實現(xiàn)時光軸效果

    Android自定義recyclerView實現(xiàn)時光軸效果

    這篇文章主要介紹了Android自定義recyclerView實現(xiàn)時光軸效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • 詳解Android中Handler的實現(xiàn)原理

    詳解Android中Handler的實現(xiàn)原理

    這篇文章主要為大家詳細(xì)介紹了Android中Handler的實現(xiàn)原理,本文深入分析 Android 的消息處理機制,了解 Handler 的工作原理,感興趣的小伙伴們可以參考一下
    2016-04-04
  • Android用動畫顯示或隱藏視圖

    Android用動畫顯示或隱藏視圖

    大家好,本篇文章主要講的是Android用動畫顯示或隱藏視圖,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-01-01
  • Android入門之Glide顯示網(wǎng)絡(luò)圖片高版本的使用詳解

    Android入門之Glide顯示網(wǎng)絡(luò)圖片高版本的使用詳解

    這篇文章主要為大家詳細(xì)介紹了Android中Glide顯示網(wǎng)絡(luò)圖片高版本的使用方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下
    2023-02-02
  • Android自定義Gradle插件的詳細(xì)過程

    Android自定義Gradle插件的詳細(xì)過程

    Groovy語言是一種jvm語言,最終也是編譯成class文件然后在jvm上執(zhí)行,所以所有的Java語言的特性Groovy都支持,我們可以完全混寫Java和Groovy,對Android自定義Gradle插件相關(guān)知識,感興趣的朋友跟隨小編一起看看吧
    2021-07-07
  • 詳解Android獲取所有依賴庫的幾種方式

    詳解Android獲取所有依賴庫的幾種方式

    本篇文章主要介紹了詳解Android獲取所有依賴庫的幾種方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-07-07
  • 安卓版微信小程序跳一跳輔助

    安卓版微信小程序跳一跳輔助

    這篇文章主要為大家詳細(xì)介紹了安卓版微信小程序跳一跳輔助,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • Android仿微信照片選擇器實現(xiàn)預(yù)覽查看圖片

    Android仿微信照片選擇器實現(xiàn)預(yù)覽查看圖片

    這篇文章主要介紹了Android仿微信照片選擇器實現(xiàn)預(yù)覽查看圖片的相關(guān)資料,感興趣的小伙伴們可以參考一下
    2016-01-01
  • ProgressBar、ProgessDialog-用法(詳解)

    ProgressBar、ProgessDialog-用法(詳解)

    下面小編就為大家?guī)硪黄狿rogressBar、ProgessDialog-用法(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Android屏幕分辨率工具類使用詳解

    Android屏幕分辨率工具類使用詳解

    這篇文章主要為大家詳細(xì)介紹了Android屏幕分辨率工具類使用,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-09-09

最新評論