English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
RecyclerViewはすでに長い間存在していますが、プロジェクトでは以前はListViewを使用していました。最近のプロジェクトでは、RecycleViewが大量に使用されています。特に、リストの上から下拉してリフレッシュするアニメーションは、ネット上には適切なものが見つからなかったので、自分でまとめました。
まず画像を貼り付けます:
RecyclerViewを使用して上から読み込むとさらに読み込む機能と下拉リフレッシュの機能を実現する方法は2種類あります:
1を使用して4widget.SwipeRefreshLayoutを使用して実現されます。
2に含まれる、RecyleViewを持つカスタムウィジェット。
RecycleViewを使用すると、ヘッダーを追加するのが非常に難しいです。以前、ListViewを使用していたときは、ヘッダーやボトムを自分で追加できましたが、RecycleViewは操作が難しいようです。まず、システムが提供するAndroid.support.v4widget.SwipeRefreshLayoutで実現されていますが、使いやすく、しかし、製品はこのような下拉リフレッシュを使わないことが多いです。自分を素晴らしいように見せたいので、彼は自分のアニメーション付きのものを使うことが多いです。これは少し不自然です。だからこそ、この方法を使うしかありません。2です。
方法について簡単に説明します:2の実現方法は、親レイアウトがViewGroupであり、内にViewを追加し、最初のウィジェットがヘッダーで、次のウィジェットがRecycleViewである。最下部の下拉してさらに読み込むビューは、RecycleViewのAdapterを通じて追加される。
考え方が決まったら動いてみましょう:
package com.krain.srecyclerview.fruitview; import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import com.krain.srecyclerview.R; /** * dafuShaoによって作成されました 2016/9/9 0009. * */ public class ElizabethView extends FrameLayout { private ImageView imageView; private AnimationDrawable animationDrawable; public ElizabethView(Context context) { super(context); initview(context); } public ElizabethView(Context context, AttributeSet attrs) { super(context, attrs); initview(context); } public ElizabethView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initview(context); } private void initview(Context context){ View view= LayoutInflater.from(context).inflate(R.layout.elizabeth_item,null); imageView=(ImageView) view.findViewById(R.id.elizabeth_im); animationDrawable= (AnimationDrawable) imageView.getBackground(); addView(view); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } //アニメーションを開始します public void startAnim(){ animationDrawable.start(); } //アニメーションを停止します public void stopAnim(){ animationDrawable.stop(); } }
これはヘッダーコントロールで非常にシンプルで、小さなアヒルの目が左右に押し寄せる効果がありますが、画像を貼り付けるのは省略します。
以下はRecyclerViewを含むカスタムコントロールのコードです:
package com.krain.srecyclerview.srecyclerview; import android.content.Context; import android.os.Handler; import android.os.Message; import android.support.v4.view.MotionEventCompat; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.Scroller; import android.widget.TextView; import com.krain.srecyclerview.R; import com.krain.srecyclerview.fruitview.ElizabethView; public class SRecyclerView extends ViewGroup { Context context; RecyclerView mRecyclerView; ElizabethView mHeaderView; TextView mFootViewTips;//footviewのテキスト表示 AdapterWrapper mAdapter; boolean mIsTop = true;//最上部にスライドしたかどうか RecyclerView.LayoutManager mLayoutManager; int mLastVisibleItem; int mFirstVisibleItem; OnRecyclerStatusChangeListener mRecyclerChangeListener; int mStatus;//現在の状態 int mHeadviewHeight;//headviewの高さ Scroller mScroller; int mFristScollerY;//最初のgetscrolly boolean mHasFooter;//上押しロード機能があるかどうか boolean mShowFootVisible;//mHasFooterがtrueのときにFOOTERviewを表示するかどうか boolean mHasRefresh = true;//下押しリフレッシュをサポートするかどうか private final int DEFAULT_MIN_PAGEINDEX = 1;//デフォルトの最小ページ数 int mMaxPage = DEFAULT_MIN_PAGEINDEX;//ページングの合計ページ数 int mCurrentPage = DEFAULT_MIN_PAGEINDEX;//現在のページ数、1開始 private final int STATUS_NORMAL = 0, STATUS_REFRESH = 1, STATUS_LOAD = 2; private final int MSG_LOAD_COMPLETE = 1, MSG_REFRESH_COMPLETE = 0;//handleの定数 private final int DELAY_LOAD_COMPLETE = 1000, DELAY_REFRESH_COMPLETE = 1000;//加载完成后回收的延时时间 public SRecyclerView(Context context) { super(context); init(context); } public SRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public SRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } /** * 最大页数设置 * * @param maxPage */ public void setMaxPage(int maxPage) { this.mMaxPage = maxPage; } /** * 上拉加载是否支持 * * @param hasLoadmore */ public void setLoadmore(boolean hasLoadmore) { mHasFooter = hasLoadmore;} } public void setRecyclerViewLayoutManage(RecyclerView.LayoutManager mLayoutManage){ this.mLayoutManager=mLayoutManage; } /** * 下押しリフレッシュ機能を無効にします */ public void disableRefresh() { mHasRefresh = false; } public void setAdapter(BaseRecyclerViewAdapter adapter) { int height = 0; if (mMaxPage == DEFAULT_MIN_PAGEINDEX) { mHasFooter = false; } mAdapter = new AdapterWrapper(context, adapter); mRecyclerView.setAdapter(mAdapter); } private int getViewHeight(View view) { int measure = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); view.measure(measure, measure); return view.getMeasuredHeight(); } /** * ViewGroup内のRecyclerViewを取得します * * @return */ public RecyclerView getRecyclerView() { return mRecyclerView; } public void setOnRecyclerChangeListener(OnRecyclerStatusChangeListener listener) { mRecyclerChangeListener = listener; } /** * RecyclerViewに追加、削除するItemのアニメーションを設定します * * @param animator */ public void setItemAnimator(RecyclerView.ItemAnimator animator) { mRecyclerView.setItemAnimator(animator); } mAdapter.notifyDataSetChanged(); mStatus = STATUS_NORMAL;//mAdapter.notifyItemRemoved(position); public void notifyDataInsert(int positionStart, int itemCount) { } mAdapter.notifyItemRangeInserted(positionStart, itemCount); mStatus = STATUS_NORMAL;//mAdapter.notifyItemRemoved(position); public void notifyDataRemove(int position) { } データを再セットして、loadmoreが終了したことを示し、通常状態に復帰する mStatus = STATUS_NORMAL;//mAdapter.notifyItemRemoved(position); 初期化操作 } /** * void init(Context context) { * * @param context */ this.context = context; mScroller = new Scroller(context); if (mLayoutManager != null) { // addChildView(context, mLayoutManager); // else { // } // addChildView(context, new LinearLayoutManager(context)); // } addChildView(context); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } /** * 子Viewを追加する * * @param context */ void addChildView(Context context, RecyclerView.LayoutManager mLayoutManager) { ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); mHeaderView = new ElizabethView(context); mRecyclerView = new RecyclerView(context); addView(mHeaderView); addView(mRecyclerView); //mLayoutManager = new LinearLayoutManager(context); // mLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); mRecyclerView.setLayoutManager(mLayoutManager); // Itemの追加、削除のデフォルトアニメーションを設定します mRecyclerView.setItemAnimator(new DefaultItemAnimator()); mRecyclerView.addOnScrollListener(onScrollListener); mRecyclerView.setHasFixedSize(true); mRecyclerView.setLayoutParams(params); } void addChildView(Context contex) { ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); mHeaderView = new ElizabethView(context); mRecyclerView = new RecyclerView(context); addView(mHeaderView); addView(mRecyclerView); //mLayoutManager = new LinearLayoutManager(context); mLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); mRecyclerView.setLayoutManager(mLayoutManager); // Itemの追加、削除のデフォルトアニメーションを設定します mRecyclerView.setItemAnimator(new DefaultItemAnimator()); mRecyclerView.addOnScrollListener(onScrollListener); mRecyclerView.setHasFixedSize(true); mRecyclerView.setLayoutParams(params); } /** * Recyclerviewのタッチイベントを遮断する(プルダウンリフレッシュの際) */ float lastY; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); switch (action) { case MotionEvent.ACTION_DOWN: lastY = ev.getRawY(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: return false; case MotionEvent.ACTION_MOVE: if (mHasRefresh && mIsTop && ev.getRawY() > lastY) return true; break; } return false; } float offsetY = 0; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: offsetY = Math.abs(event.getRawY() - lastY); //Yの差の絶対値 if (offsetY > 0) scrollToOffset(offsetY); else { mIsTop = false; } break; case MotionEvent.ACTION_UP: if (getScrollY() <= 0) { doRefresh(); mHeaderView.stopAnim(); mHeaderView.startAnim(); } else complete(); break; } return super.onTouchEvent(event); } /** * このビューを手でスライドした位置にスクロール * * @param offsetY Y軸オフセット量 */ void scrollToOffset(float offsetY) { //現在が更新中で、現在のscrollyと初期値が同じ場合、下拉開始更新の準備ができており、onceを実行します if (getScrollY() == mFristScollerY && mRecyclerChangeListener != null) mRecyclerChangeListener.startRefresh(); int value = Math.round(offsetY / 2.0F); value = mFristScollerY - value; scrollTo(0, value); } /** * 実行更新操作、ヘッダーが最初に現れた位置に移動 */ void doRefresh() { mStatus = STATUS_REFRESH; int currentY = getScrollY(); mScroller.startScroll(0, currentY, 0, (mFristScollerY - mHeadviewHeight) - currentY); invalidate(); if (mRecyclerChangeListener != null) mRecyclerChangeListener.onRefresh(); handler.sendEmptyMessageDelayed(MSG_REFRESH_COMPLETE, DELAY_REFRESH_COMPLETE); } Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == MSG_LOAD_COMPLETE) { View footview = mAdapter.getFootView(); if (footview != null) mRecyclerView.smoothScrollBy(0, -footview.getMeasuredHeight()); } else if (msg.what == MSG_REFRESH_COMPLETE) complete(); } }; /** * header返回原处完全隐藏 */ public void complete() { mCurrentPage = DEFAULT_MIN_PAGEINDEX;//完成之后当前的page恢复默认值 if (mFootViewTips != null) mFootViewTips.setText(context.getString(R.string.loading));//変更foot提示为正在加载中 if (mRecyclerChangeListener != null) mRecyclerChangeListener.refreshComplete(); mStatus = STATUS_NORMAL; int currentY = getScrollY(); mScroller.startScroll(0, currentY, 0, mFristScollerY - currentY); mHeaderView.stopAnim(); invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); height += child.getMeasuredHeight(); } setMeasuredDimension(width, height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int left = getPaddingLeft(); int top = getPaddingTop(); for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (i == 0) {//ヘッダーの場合は中央に表示 int headerLeft = getMeasuredWidth(); / 2 - child.getMeasuredWidth(); / 2; child.layout(headerLeft, top, headerLeft + child.getMeasuredWidth(), top + child.getMeasuredHeight()); } else child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); top += child.getMeasuredHeight(); } mHeadviewHeight = getPaddingTop(); + mHeaderView.getMeasuredHeight(); scrollTo(0, mHeadviewHeight);//ヘッダー下に移動してrecyleviewを表示 mFristScollerY = getScrollY(); } /** * RecyclerViewのスライドリスナーイベント */ RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE) { //顶部にスライドしました if (mFirstVisibleItem == 0) { mIsTop = true; } else { mIsTop = false; if (mStatus != STATUS_LOAD && mShowFootVisible && mLastVisibleItem + 1 == mAdapter.getItemCount()) { if (mCurrentPage == mMaxPage) { //現在のページが最後のページである場合 mFootViewTips = (TextView) mAdapter.getFootView().findViewById(R.id.footer_tips); mFootViewTips.setText(context.getString(R.string.last_page_tips)); handler.sendEmptyMessageDelayed(MSG_LOAD_COMPLETE, DELAY_LOAD_COMPLETE); } else { mStatus = STATUS_LOAD; if (mRecyclerChangeListener != null) { mRecyclerChangeListener.onLoadMore(); mCurrentPage++; } } } } } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (mLayoutManager instanceof LinearLayoutManager) { mLastVisibleItem = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition(); mFirstVisibleItem = ((LinearLayoutManager) mLayoutManager).findFirstCompletelyVisibleItemPosition(); setFootviewVisible(); } else if (mLayoutManager instanceof GridLayoutManager) { mLastVisibleItem = ((GridLayoutManager) mLayoutManager).findLastVisibleItemPosition(); mFirstVisibleItem = ((GridLayoutManager) mLayoutManager).findFirstCompletelyVisibleItemPosition(); setFootviewVisible(); } else if (mLayoutManager instanceof StaggeredGridLayoutManager) { //StaggeredGridLayoutManagerの特殊性により、最後に表示されるitemが複数存在する可能性があるため、配列として取得しています //この配列を取得した後、配列の中でposition値が最大のものが最後に表示されるposition値となります int[] lastPositions = new int[((StaggeredGridLayoutManager) mLayoutManager).getSpanCount()]; ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(lastPositions); mLastVisibleItem = findMax(lastPositions); mFirstVisibleItem = ((StaggeredGridLayoutManager) mLayoutManager).findFirstVisibleItemPositions(lastPositions)[0]; setFootviewVisible(); } } }; void setFootviewVisible() { //上拉ロード機能が設定されているが、第一ページのエントリがRecyclerViewを満たすことができない場合、footerを非表示にします if (mHasFooter && mFirstVisibleItem == 0) { /** * ここにmShowFootVisibleを追加し、上拉ロード機能が有効な場合に効果を発揮し、item数が不足してviewを埋め尽くさないことを制御 * recyclerviewの高さの瞬間にfootviewを非表示にし、item数がviewの高さを超える場合に表示 */ if (mLastVisibleItem + 1 == mAdapter.getItemCount()) { mShowFootVisible = false; } else mShowFootVisible = true; notifyDataSetChanged(); } } private int findMax(int[] positions) { int max = positions[0]; for (int value : positions) { if (value > max) { max = value; } } return max; } private class AdapterWrapper extends RecyclerView.Adapter { private static final int TYPE_ITEM = 0; private static final int TYPE_FOOTER = 1; private RecyclerView.Adapter mAdapter; private Context mContext; View footer; public AdapterWrapper(Context context, RecyclerView.Adapter wrappedAdapter) { this.mContext = context; this.mAdapter = wrappedAdapter; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder holder = null; switch (viewType) { case TYPE_ITEM: holder = mAdapter.onCreateViewHolder(parent, viewType); break; case TYPE_FOOTER: footer = LayoutInflater.from(mContext).inflate(R.layout.lib_recyle_footview, null); LinearLayout linearLayout= (LinearLayout) footer.findViewById(R.id.loading_layout); StaggeredGridLayoutManager.LayoutParams layoutParams = new StaggeredGridLayoutManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); layoutParams.setFullSpan(true); linearLayout.setLayoutParams(layoutParams); holder = new FooterViewHolder(footer); break; } return holder; } public View getFootView() { return footer; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (!mHasFooter || position + 1 != getItemCount()) { mAdapter.onBindViewHolder(holder, position); } } @Override public int getItemCount() { return mShowFootVisible ? mAdapter.getItemCount() + 1 : mAdapter.getItemCount(); } @Override public int getItemViewType(int position) { if (mShowFootVisible && position + 1 == getItemCount()) { return TYPE_FOOTER; } else { return TYPE_ITEM; } } private class FooterViewHolder extends RecyclerView.ViewHolder { public ProgressBar progressBar; public TextView tvLoading; public LinearLayout llyLoading; public FooterViewHolder(View itemView) { super(itemView); progressBar = (ProgressBar) itemView.findViewById(R.id.progress_loading); tvLoading = (TextView) itemView.findViewById(R.id.footer_tips); llyLoading = (LinearLayout) itemView.findViewById(R.id.loading_layout); } } } }
最後にAdapterのコードがあります:
package com.krain.srecyclerview.srecyclerview; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; public abstract class BaseRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> { private OnItemClickLisener mItemListener; @Override public VH onCreateViewHolder(ViewGroup parent, int viewType) { View view = getItemView(viewType, parent); VH vh = getViewHolder(view); view.setOnClickListener(new OnRecyclerAdapterclickListener(vh, viewType)); view.setOnLongClickListener(new OnRecyclerAdapterclickListener(vh, viewType)); return vh; } public abstract VH getViewHolder(View itemView); /** * itemのViewを返します * * @return */ public abstract View getItemView(int viewType, ViewGroup parent); /** * Adapterの各itemのデータを返します。オプション */ public Object getItem(int position) { return null; } /** * itemクリックイベントインターフェース * * @param mItemListener */ public void setOnItemListener(OnItemClickLisener mItemListener) { this.mItemListener = mItemListener; } @Override public abstract void onBindViewHolder(VH holder, int position); @Override public abstract int getItemCount(); class OnRecyclerAdapterclickListener implements View.OnClickListener, View.OnLongClickListener { VH viewholder; int viewType; public OnRecyclerAdapterclickListener(VH viewholder, int viewType) { this.viewholder = viewholder; this.viewType = viewType; } @Override public void onClick(View v) { if (mItemListener != null && viewholder.getAdapterPosition() != RecyclerView.NO_POSITION) { mItemListener.onItemClick(viewholder.getAdapterPosition(), viewType, viewholder, v); } } @Override public boolean onLongClick(View v) { if (mItemListener != null && viewholder.getAdapterPosition() != RecyclerView.NO_POSITION) { mItemListener.onItemLongClick(viewholder.getAdapterPosition(), viewType, viewholder, v); } return false; } } }
注意が必要です:
Reciviewのレイアウト方法を変更したい場合はこの部分で修正してください。
void addChildView(Context contex) { ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); mHeaderView = new ElizabethView(context); mRecyclerView = new RecyclerView(context); addView(mHeaderView); addView(mRecyclerView); //mLayoutManager = new LinearLayoutManager(context); mLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); mRecyclerView.setLayoutManager(mLayoutManager); // Itemの追加、削除のデフォルトアニメーションを設定します mRecyclerView.setItemAnimator(new DefaultItemAnimator()); mRecyclerView.addOnScrollListener(onScrollListener); mRecyclerView.setHasFixedSize(true); mRecyclerView.setLayoutParams(params); }
mLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);この行が目的です。対応するものに変更してください。ここではエンケージしていないため、数日後にエンケージします。
さらに注意すべきポイントは、スライド中に最終行かどうかを判断することです。それはフラッシュ流の場合、判断方法が他と異なるため注意が必要です
if (mLayoutManager instanceof StaggeredGridLayoutManager) { //StaggeredGridLayoutManagerの特殊性により、最後に表示されるitemが複数存在する可能性があるため、配列として取得しています //この配列を取得した後、配列の中でposition値が最大のものが最後に表示されるposition値となります int[] lastPositions = new int[((StaggeredGridLayoutManager) mLayoutManager).getSpanCount()]; ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(lastPositions); mLastVisibleItem = findMax(lastPositions); mFirstVisibleItem = ((StaggeredGridLayoutManager) mLayoutManager).findFirstVisibleItemPositions(lastPositions)[0]; setFootviewVisible(); }
StaggeredGridLayoutManagerの特殊性により、最後に表示されるitemが複数存在する可能性があります。したがって、ここでは配列を取得し、配列からposition値が最大のものを取得し、それが最後に表示されるposition値です。それから、最終行の加負荷表示を設定します。
以上の内容は編集者が皆さんに紹介したAndroid RecyclerViewの上提げ加負荷と下引きリフレッシュ機能の実現方法であり、皆さんに役立つことを願っています。何かご不明な点があれば、コメントを残してください。編集者は迅速に回答します。このサイトへのサポートに感謝しています!
声明:この記事の内容はインターネットから取得しており、著作権者所有、インターネットユーザーが自発的に貢献し、自己でアップロードしました。このウェブサイトは所有権を持ちません。人工的な編集は行われていません。また、関連する法的責任を負いません。著作権に抵触する内容が見つかった場合は、notice#w までメールを送信してください。3codebox.com(メールを送信する際には、#を@に置き換えてください。報告を行い、関連する証拠を提供してください。一旦確認がついたら、このサイトは即座に侵害される可能性のある内容を削除します。)