English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
RecyclerView já está presente há muito tempo, mas nos projetos anteriores, usamos ListView, e recentemente, muitos novos projetos estão usando RecycleView em grande quantidade. Especialmente a atualização de arrastar para baixo de cascata, muitas informações na internet, mas não achei nada adequado, então resumo meu próprio.
Vamos ver algumas imagens primeiro:
Existem duas maneiras de usar RecyclerView para implementar funções de carregar mais para cima e atualizar arrastando para baixo:
1usando o Android.support.v4para implementar o .widget.SwipeRefreshLayout.
2controle personalizado que contém RecyleView.
Adicionar cabeçalho ao RecycleView é muito ruim, antes de usar o listview, eu podia adicionar header e bootm, mas o RecycleView parece não ser tão fácil de manipular. Para o primeiro, uso o Android.support.v4implementado por .widget.SwipeRefreshLayout, também muito útil, mas os produtos geralmente não gostam desse tipo de atualização de arrastar para baixo, para se destacar, eles geralmente criam seus próprios com animação, o que é um pouco estúpido... então só podemos usar o método2.
Vamos falar brevemente sobre o método2A implementação, o layout pai é ViewGroup, adiciona View, o primeiro controle é header, o segundo controle é RecycleView, e o ícone de carregar mais na parte inferior é adicionado através do Adapter do RecycleViw.
Com uma ideia em mente, vamos começar:
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; /** * Criado por dafuShao em 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;//Exibição de texto do footview AdapterWrapper mAdapter; boolean mIsTop = true;//Se foi arrastado até o topo RecyclerView.LayoutManager mLayoutManager; int mLastVisibleItem; int mFirstVisibleItem; OnRecyclerStatusChangeListener mRecyclerChangeListener; int mStatus;//Estado atual int mHeadviewHeight;//Altura do headview Scroller mScroller; int mFristScollerY;//O scroll inicial mais recente boolean mHasFooter;//Se há função de carregamento para baixo boolean mShowFootVisible;//Se o FOOTERview deve ser exibido quando mHasFooter é true boolean mHasRefresh = true;//Se suporta atualização para baixo private final int DEFAULT_MIN_PAGEINDEX = 1;//Número mínimo padrão int mMaxPage = DEFAULT_MIN_PAGEINDEX;//Número total de páginas de paginação int mCurrentPage = DEFAULT_MIN_PAGEINDEX;//Página atual, a partir de1Início private final int STATUS_NORMAL = 0, STATUS_REFRESH = 1, STATUS_LOAD = 2; private final int MSG_LOAD_COMPLETE = 1, MSG_REFRESH_COMPLETE = 0;//Constantes do handle private final int DELAY_LOAD_COMPLETE = 1000, DELAY_REFRESH_COMPLETE = 1000;//Tempo de recolhimento pós-carregamento 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); } /** * Definir o número máximo de páginas * * @param maxPage */ public void setMaxPage(int maxPage) { this.mMaxPage = maxPage; } /** * Se suporta carregamento para baixo * * @param hasLoadmore */ public void setLoadmore(boolean hasLoadmore) { mHasFooter = hasLoadmore; } public void setRecyclerViewLayoutManage(RecyclerView.LayoutManager mLayoutManage){ this.mLayoutManager=mLayoutManage; } /** * desativar a função de atualização puxando para baixo */ 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(); } /** * obter o RecyclerView dentro do viewgroup * * @return */ public RecyclerView getRecyclerView() { return mRecyclerView; } public void setOnRecyclerChangeListener(OnRecyclerStatusChangeListener listener) { mRecyclerChangeListener = listener; } /** * definir a animação de adição e exclusão de itens do RecyclerView * * @param animator */ public void setItemAnimator(RecyclerView.ItemAnimator animator) { mRecyclerView.setItemAnimator(animator); } public void notifyDataSetChanged() { mStatus = STATUS_NORMAL;//Reconfigurar os dados para representar o fim da operação de carregamento adicional, retornando ao estado normal mAdapter.notifyDataSetChanged(); } public void notifyDataInsert(int positionStart, int itemCount) { mStatus = STATUS_NORMAL;//Reconfigurar os dados para representar o fim da operação de carregamento adicional, retornando ao estado normal mAdapter.notifyItemRangeInserted(positionStart, itemCount); } public void notifyDataRemove(int position) { mStatus = STATUS_NORMAL;//Reconfigurar os dados para representar o fim da operação de carregamento adicional, retornando ao estado normal mAdapter.notifyItemRemoved(position); } /** * Operações de inicialização * * @param context */ void init(Context context) { this.context = context; mScroller = new Scroller(context); // if (mLayoutManager!=null){ // addChildView(context,mLayoutManager); // } // addChildView(context, new LinearLayoutManager(context)); // } addChildView(context); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } /** * Adicionar View filha * * @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); // Definir animação padrão de adição e remoção de itens 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); // Definir animação padrão de adição e remoção de itens mRecyclerView.setItemAnimator(new DefaultItemAnimator()); mRecyclerView.addOnScrollListener(onScrollListener); mRecyclerView.setHasFixedSize(true); mRecyclerView.setLayoutParams(params); } /** * Bloquear o evento de toque do Recyclerview (ao puxar para baixo para atualizar) */ 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); //Valor absoluto da diferença Y if (offsetY > 0) scrollToOffset(offsetY); else { mIsTop = false; } break; case MotionEvent.ACTION_UP: if (getScrollY() <= 0) { doRefresh(); mHeaderView.pararAnim(); mHeaderView.startAnim(); } else complete(); break; } return super.onTouchEvent(event); } /** * Rollar este view para a posição onde o dedo rolou * * @param offsetY Quantidade de deslocamento em Y */ void scrollToOffset(float offsetY) { //Se estiver atualizando e o valor atual de scrolly for igual ao valor inicial, isso significa que está pronto para começar a atualização puxando para baixo e executar uma vez only if (getScrollY() == mFristScollerY && mRecyclerChangeListener != null) mRecyclerChangeListener.startRefresh(); int value = Math.round(offsetY / 2.0F); value = mFristScollerY - value; scrollTo(0, value); } /** * Executar operação de atualização, mover para a posição do header recém-aparecido */ void doRefresh() { mStatus = STATUS_REFRESH; int currentY = getScrollY(); mScroller.startScroll(0, currentY, 0, (mFristScollerY - mHeadviewHeight) - currentY); invalidar(); 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 volta ao lugar e esconde completamente */ public void complete() { mCurrentPage = DEFAULT_MIN_PAGEINDEX;//Depois de concluído, a página atual volta ao valor padrão if (mFootViewTips != null) mFootViewTips.setText(context.getString(R.string.loading));//alterar a dica de pé para carregando if (mRecyclerChangeListener != null) mRecyclerChangeListener.refreshComplete(); mStatus = STATUS_NORMAL; int currentY = getScrollY(); mScroller.iniciarScrollamento(0, currentY, 0, mFristScollerY - currentY); mHeaderView.pararAnim(); invalidar(); } @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) {//Quando é o header, exibir no centro 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);//Mover para baixo do header para exibir o recyleview mFristScollerY = getScrollY(); } /** * O evento de escorrimento do 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) { //Foi arrastado para o topo if (mFirstVisibleItem == 0) { mIsTop = true; } else { mIsTop = false; if (mStatus != STATUS_LOAD && mShowFootVisible && mLastVisibleItem + 1 == mAdapter.getItemCount()) { if (mCurrentPage == mMaxPage) { //Quando a página atual é a última página 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) { //Devido à peculiaridade do StaggeredGridLayoutManager, pode haver vários itens exibidos no final, portanto, aqui é um array //Depois de obter esse array, pegar o valor de position no array mais alto é o valor de position exibido no final 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数量不足铺满 * 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); } } } }
Último, há ainda o código do 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); /** * retorna a view do item * * @return */ public abstract View getItemView(int viewType, ViewGroup parent); /** * retorna os dados de cada item do Adapter, opcional */ public Object getItem(int position) { return null; } /** * interface de clique de 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; } } }
Atenção:
Se quiser alterar o estilo de layout do Reciview, faça essa modificação aqui;
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); // Definir animação padrão de adição e remoção de itens mRecyclerView.setItemAnimator(new DefaultItemAnimator()); mRecyclerView.addOnScrollListener(onScrollListener); mRecyclerView.setHasFixedSize(true); mRecyclerView.setLayoutParams(params); }
mLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); Esta linha é, altere para o correspondente, aqui não foi encapsulado, será encapsulado em alguns dias.
Também é necessário prestar atenção se é a última linha ao deslizar, se for um waterfall, atenção à maneira de julgamento diferente das outras
if (mLayoutManager instanceof StaggeredGridLayoutManager) { //Devido à peculiaridade do StaggeredGridLayoutManager, pode haver vários itens exibidos no final, portanto, aqui é um array //Depois de obter esse array, pegar o valor de position no array mais alto é o valor de position exibido no final int[] lastPositions = new int[((StaggeredGridLayoutManager) mLayoutManager).getSpanCount()]; ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(lastPositions); mLastVisibleItem = findMax(lastPositions); mFirstVisibleItem = ((StaggeredGridLayoutManager) mLayoutManager).findFirstVisibleItemPositions(lastPositions)[0]; setFootviewVisible(); }
Devido à característica específica do StaggeredGridLayoutManager, pode haver vários itens exibidos no final, então aqui é obtido um array. Após obter o array, pega-se o valor de position mais alto no array, que é o valor de position exibido no final. Em seguida, configura-se a exibição de carregamento adicional na última linha.
O que foi mencionado acima é o método de implementação da função de carregamento adicional para baixo e atualização de arrastamento para baixo no Android RecyclerView apresentado pelo editor. Esperamos que isso ajude a todos. Se você tiver alguma dúvida, por favor, deixe um comentário e o editor responderá a tempo. Agradecemos também o apoio aos endereços do site do tutorial de clamor!
Declaração: O conteúdo deste artigo é de origem na internet, pertencente ao respectivo proprietário. O conteúdo é contribuído e carregado voluntariamente pelos usuários da internet. Este site não possui direitos de propriedade, não foi editado manualmente e não assume responsabilidades legais relacionadas. Se você encontrar conteúdo suspeito de violação de direitos autorais, por favor, envie um e-mail para: notice#oldtoolbag.com (ao enviar e-mail, substitua # por @ para denunciar. Forneça provas relevantes e, se confirmado, o site deletará o conteúdo suspeito de violação de direitos autorais imediatamente.