編輯:關於Android編程
用戶通過手勢或者點擊某個按鈕實現內容視圖的刷新,布局裡加入SwipeRefreshLayout嵌套一個子視圖如ListView、RecyclerView等,觸發刷新會通過OnRefreshListener的onRefresh方法回調,我們在這裡執行頁面數據的刷新,每次手勢的完成都會執行一次通知,根據滑動距離判斷是否需要回調。setRefreshing(false)通過代碼直接取消刷新,true則手動設置刷新調出刷新視圖。setEnabled(false)通過boolean控制是否禁用手勢刷新
public class SwipeRefreshLayoutBasicFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//...........=略................
mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swiperefresh);
// 設置下拉刷新的圓的顏色
mSwipeRefreshLayout.setColorScheme(
R.color.swipe_color_1, R.color.swipe_color_2,
R.color.swipe_color_3, R.color.swipe_color_4);
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//初始化ListView布局
mListView.setAdapter(adapter);
//綁定視圖刷新的監聽
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//TODO
//重新獲取完網絡數據刷新Adapter,完成後需要調用onRefreshComplete方法取消滑出來的圓形進度
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
//手動點擊按鈕刷新視圖如果當前視圖狀態沒有刷新需要調用setRefreshing(true)
if (!mSwipeRefreshLayout.isRefreshing()) {
mSwipeRefreshLayout.setRefreshing(true);
}
initiateRefresh();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* 刷新Adapter並且取消刷新滑出來的進度視圖
**/
private void onRefreshComplete(List result) {
mSwipeRefreshLayout.setRefreshing(false);
adapter.onRefresh(result)
}
}
在官網找到三個simple,前面兩個simple主要是上面提到的基本用法,對我們來說用處不大
SwipeRefreshLayoutBasic
SwipeRefreshListFragment
SwipeRefreshMultipleViews
在SwipeRefreshMultipleViews Simple裡面提供了一個SwipeRefreshLayout的繼承類MultiSwipeRefreshLayout,該類的作用用於子視圖列表添加EmptyView,v4包裡面沒有這裡貼上源碼:
public class MultiSwipeRefreshLayout extends SwipeRefreshLayout {
private View[] mSwipeableChildren;
public MultiSwipeRefreshLayout(Context context) {
super(context);
}
public MultiSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Set the children which can trigger a refresh by swiping down when they are visible. These
* views need to be a descendant of this view.
*/
public void setSwipeableChildren(final int... ids) {
assert ids != null;
mSwipeableChildren = new View[ids.length];
for (int i = 0; i < ids.length; i++) {
mSwipeableChildren[i] = findViewById(ids[i]);
}
}
/**
* This method controls when the swipe-to-refresh gesture is triggered. By returning false here
* we are signifying that the view is in a state where a refresh gesture can start.
*
*
As {@link android.support.v4.widget.SwipeRefreshLayout} only supports one direct child by * default, we need to manually iterate through our swipeable children to see if any are in a * state to trigger the gesture. If so we return false to start the gesture. */ @Override public boolean canChildScrollUp() { if (mSwipeableChildren != null && mSwipeableChildren.length > 0) { for (View view : mSwipeableChildren) { if (view != null && view.isShown() && !canViewScrollUp(view)) { return false; } } } return true; } /** * Utility method to check whether a {@link View} can scroll up from it's current position. * Handles platform version differences, providing backwards compatible functionality where * needed. */ private static boolean canViewScrollUp(View view) { if (android.os.Build.VERSION.SDK_INT >= 14) { return ViewCompat.canScrollVertically(view, -1); } else { if (view instanceof AbsListView) { final AbsListView listView = (AbsListView) view; return listView.getChildCount() > 0 && (listView.getFirstVisiblePosition() > 0 || listView.getChildAt(0).getTop() < listView.getPaddingTop()); } else { return view.getScrollY() > 0; } } } }
<framelayout android:layout_height="match_parent" android:layout_width="match_parent">
</framelayout>
public class SwipeRefreshMultipleViewsFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//.............略...........
mSwipeRefreshLayout = (MultiSwipeRefreshLayout) view.findViewById(R.id.swiperefresh);
//設置進度圓形的顏色
mSwipeRefreshLayout.setColorScheme(
R.color.swipe_color_1, R.color.swipe_color_2,
R.color.swipe_color_3, R.color.swipe_color_4);
mGridView = (GridView) view.findViewById(android.R.id.list);
mEmptyView = view.findViewById(android.R.id.empty);
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mGridView.setAdapter(adapter);
// 當GridView沒有數據時顯示EmptyView
mGridView.setEmptyView(mEmptyView);
//設置需要Refresh視圖
mSwipeRefreshLayout.setSwipeableChildren(android.R.id.list, android.R.id.empty);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//TODO
}
});
}
}
在官方提供的simple裡面發現,EmptyView顯示時,進度視圖可能無法顯示處理,但是OnRefreshListener的回調還是執行了,根據我的懷疑做了一個小小的實驗,取消了setEmpty方法,發現就沒問題,不過疊加顯示會有問題了,需要手動控制Visibility,根據以上知識做了一個簡單的demo實踐(雞湯:主題報錯A TaskDescription’s primary color should be opaque,原因是顏色值缺損,需要補齊00-FF),效果圖如下,奉上源碼:http://download.csdn.net/detail/analyzesystem/9508674
自定義控件SwipeRefreshLayout是一個自定義ViewGroup,這裡面用到的NestedScrolling系列之前BottomBar篇提到過這裡就不再累贅敘述,自定義的ViewGroup內部涉及到MaterialProgressDrawable進度圖片、CircleImageView(v4包裡面的不是開源庫那個),圓形進度圖片的一些方法在SwipeRefreshLayout裡面間接調用,下面從SwipeRefreshLayout的相關方法簡單理解。
reset方法就是調用子view的方法取消相應的動畫,並且隱藏view,setProgressViewOffset方法對我們來說還是非常有用的,用於設置CircleView的進出動畫是否執行縮放(API 11有兼容,向下無法執行縮放透明,開發兼容個人4.0+所以不存在任何問題)、以及下拉出現的位置和最大的下拉位置。
/**
* @param 設置下拉出現小圓圈是否是縮放出現
* @param 出現的位置
* @param 最大的下拉位置
**/
public void setProgressViewOffset(boolean scale, int start, int end) {
mScale = scale;
mCircleView.setVisibility(View.GONE);
mOriginalOffsetTop = mCurrentTargetOffsetTop = start;
mSpinnerFinalOffset = end;
mUsingCustomStart = true;
mCircleView.invalidate();
}
設置下拉圓圈的大小,通過注解ProgressDrawableSize兩個類型: LARGE, DEFAULT,在SwipeRefreshLayout裡面根據這兩種類型選擇不同的大小:CIRCLE_DIAMETER 、CIRCLE_DIAMETER_LARGE
private static final int CIRCLE_DIAMETER = 40;
private static final int CIRCLE_DIAMETER_LARGE = 56;
public void setSize(int size) {
if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) {
return;
}
final DisplayMetrics metrics = getResources().getDisplayMetrics();
if (size == MaterialProgressDrawable.LARGE) {
mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density);
} else {
mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
}
// force the bounds of the progress circle inside the circle view to
// update by setting it to null before updating its size and then
// re-setting it
mCircleView.setImageDrawable(null);
mProgress.updateSizes(size);
mCircleView.setImageDrawable(mProgress);
}
在構造函數內部初始化必須變量,並為ViewGroup添加一個CircleView
/**
* Constructor that is called when inflating SwipeRefreshLayout from XML.
*
* @param context
* @param attrs
*/
public SwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//系統默認的最小滑動系數值
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
//默認動畫時常400
mMediumAnimationDuration = getResources().getInteger(
android.R.integer.config_mediumAnimTime);
setWillNotDraw(false);
mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
setEnabled(a.getBoolean(0, true));
a.recycle();
final DisplayMetrics metrics = getResources().getDisplayMetrics();
mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density);
//創建CircleView並添加到ViewGroup
createProgressView();
ViewCompat.setChildrenDrawingOrderEnabled(this, true);
// the absolute offset has to take into account that the circle starts at an offset
mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density;
mTotalDragDistance = mSpinnerFinalOffset;
//通過 NestedScrolling 處理嵌套滑動
mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
判斷字視圖是否支持滑動刷新,根據View類型和ViewCompat調用底層方法判斷,如果你要自定義SwipeRefreshLayout需要重寫方法,參考示例如MultiSwipeRefreshLayout內部具體實現
/**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
*/
public boolean canChildScrollUp() {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
}
}
內部提供了幾個setColor系列的方法,其實本質在調用子View進行賦值,這裡不累贅敘述以mProgress為例
/**
* Set the colors used in the progress animation. The first
* color will also be the color of the bar that grows in response to a user
* swipe gesture.
*
* @param colors
*/
@ColorInt
public void setColorSchemeColors(int... colors) {
ensureTarget();
mProgress.setColorSchemeColors(colors);
}
再過完一遍代碼後發現,很多發放與我們使用都無關,就不細解了,如果你還想了解更多,在這裡奉上我在github搜到的關於SwipeRefreshLayout源碼分析一篇:https://github.com/hanks-zyh/SwipeRefreshLayout
SwipeRefresh開源庫推薦兩個,一個是基於ListView支持下拉刷新上啦加載更多的,一個是使用Builder構建,基於RecyclerView實現,並且同樣支持下拉刷新上啦加載更多,下面是相關鏈接
SwipeRefreshLayout
代碼調用風格:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRefreshLayout = (RefreshLayout) findViewById(R.id.swipe_container);
mListView = (ListView) findViewById(R.id.list);
footerLayout = getLayoutInflater().inflate(R.layout.listview_footer, null);
mListView.addFooterView(footerLayout);
mRefreshLayout.setChildView(mListView);
mListView.setAdapter(mAdapter);
mRefreshLayout.setColorSchemeResources(R.color.google_blue,
R.color.google_green,
R.color.google_red,
R.color.google_yellow);
mRefreshLayout.setOnRefreshListener(new RefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// start to refresh
}
});
mRefreshLayout.setOnLoadListener(new RefreshLayout.OnLoadListener() {
@Override
public void onLoad() {
// start to load
}
});
}
SwipePresenter
代碼調用風格:
presenter = new SwipePresenter.Builder()
.onCreated(new Runnable() {
@Override
public void run() {
recyclerview.setLayoutManager(new StaggeredGridLayoutManager(
2, StaggeredGridLayoutManager.VERTICAL)
);
recyclerview.setAdapter(adapter);
}
})
.swipeRefreshLayout(swipeRefreshLayout)
.recyclerView(recyclerview)
.emptyView(emptyView)
.onRefresh(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
presenter.stopRefresh();
}
})
.onLoadMore(new SwipePresenter.AutoLoadMoreListener(4) {
@Override
public void onLoadMore(RecyclerView recyclerView) {
finishLoadingMore(); // or you can replace this with presenter.finishLoadingMore();
}
})
.build();
花了一點時間整理SwipeRefreshLayout這塊的知識還是值得的,收獲也有不少,以前沒用過的方法比如setProgressViewOffset以前就沒用過,再比如MultiSwipeRefreshLayout這個意外收獲,此刻的心情是開森的,在此,借古人一句話送給大家:時而學習之不亦說乎!
SQLite是Android系統內置的數據庫,是一種輕量級的關系型數據庫,它運算速度快,占用資源少,非常適合在移動設備上使用。同時,它不僅支持標准的SQL語法,還遵循了
顯示操作進度的對話框 1、使用上一篇創建的同一項目,在activity_main.xml文件中添加一個Button: 2、在MainActivity.ja
一、實現思路1、在build.gradle中添加依賴,例如:compile com.android.support:support-v4:23.4.0compile co
才沒有完結呢o( ̄︶ ̄)n 。大家好,這裡是番外篇。拜讀了愛哥的博客,又學到不少東西。愛哥曾經說過: 要站在巨人的丁丁上。 那麼今天,我們就站在愛哥的丁丁上來學習制作一款