
新しいウィジェット
「RecyclerViewおよびCardView」に関するハブの投稿を読んだこと
。 Android Lの新しいウィジェット」 、私はそれを試してみることにしました。 CardViewがRecyclerViewに埋め込まれているネットには多くの例があります。 CardViewにRecyclerViewを埋め込むという反対に興味があります。 このデザインを持つことは断片でした。
この記事から例をダウンロードしました。 複数のアイテムを削除するとすぐに問題が発生しました。 コードを確認した後、チェックを設定しました。
private void delete(Record record) { int position = records.indexOf(record); Log.i(">" , "position=" + position); if( position != -1 ) { records.remove(position); notifyItemRemoved(position); } }
それはほんの始まりでした...フラグメントを追加した後、別の問題が出てきました。 CardViewは、RecyclerViewのリストを高さごとに正しく「ラップ」できません。 wrap_contentは役に立ちません。 多くの人がすでに遭遇し、解決策があることが判明しました:
「ネストされたリサイクルビューの高さはコンテンツをラップしません 。
」最初は、
AndroidのRecyclerViewの概要を見て、layoutManager.getDecoratedMeasuredHeight()...メソッドなどを使用することを考えましたが、それは役に立ちませんでした。 サイズが0を返しました。
LinearLayoutManagerでonMeasureを書き換える必要がありました。 stackoverflowから取得:
MyLinearLayoutManager public class MyLinearLayoutManager extends LinearLayoutManager { public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } public MyLinearLayoutManager(Context context) { super(context); } private int[] mMeasuredDimension = new int[2]; @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { final int widthMode = View.MeasureSpec.getMode(widthSpec); final int heightMode = View.MeasureSpec.getMode(heightSpec); final int widthSize = View.MeasureSpec.getSize(widthSpec); final int heightSize = View.MeasureSpec.getSize(heightSpec); int width = 0; int height = 0; for (int i = 0; i < getItemCount(); i++) { if (getOrientation() == HORIZONTAL) { measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), heightSpec, mMeasuredDimension); width = width + mMeasuredDimension[0]; if (i == 0) { height = mMeasuredDimension[1]; } } else { measureScrapChild(recycler, i, widthSpec, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), mMeasuredDimension); height = height + mMeasuredDimension[1]; if (i == 0) { width = mMeasuredDimension[0]; } } } switch (widthMode) { case View.MeasureSpec.EXACTLY: width = widthSize; case View.MeasureSpec.AT_MOST: case View.MeasureSpec.UNSPECIFIED: } switch (heightMode) { case View.MeasureSpec.EXACTLY: height = heightSize; case View.MeasureSpec.AT_MOST: case View.MeasureSpec.UNSPECIFIED: } setMeasuredDimension(width, height); } private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) { View view = recycler.getViewForPosition(position); recycler.bindViewToPosition(view, position); if (view != null) { RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams(); int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight(), p.width); int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(), p.height); view.measure(childWidthSpec, childHeightSpec); measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin; measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin; recycler.recycleView(view); } } }
稼いだ。 削除は最後からのみ可能でした。 リストの中央またはリストの先頭から削除すると、例外が発生しました。
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 3(offset:-1).state:5
変です。 もう少しグーグルで、私は
IndexOutOfBoundsExceptionの無効なアイテムの位置XX(XX)の人々と同様の問題に遭遇しました
。 アイテム数:XX#134 私が読んだすべてのトピックを見た:
実際、RecyclerViewのバグであり、まだ修正されていません。
詳細については、次を確認してください。
code.google.com/p/android/issues/detail?id=77846code.google.com/p/android/issues/detail?id=77232そして、上記の行は単なる解決策でした:
今、私はいくつかの汚い回避策をしています
if(index == 0){notifydatasetchange();} else {notifyItemRemoved(index)}より正確には、要素の削除方法を見て、notifyItemRemoved(インデックス)をnotifydatasetchange()に置き換える必要があることに気付きました。 追加しても同様です。 ソリューションは最適ではありませんが、おそらく唯一のウィジェットの現在の実装で動作します。
このような決定はアニメーションを完全に殺しました。 研究を終了することがあります...
その結果、オーバーライドされたonMeasure()に落下の場所が見つかりました
IndexOutOfBoundsException = java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 0(offset:-1).state:7
何らかの形で状況を傍受したり、事前のオフセットを要求したりするためのRecyclerViewコードのさらなる調査は成功しませんでした。 ハードハックを作りました! 厳密に判断しないでください)
View view = null; try { view = recycler.getViewForPosition(position); }catch (IndexOutOfBoundsException ex){ Log.i(">", "IndexOutOfBoundsException = " + ex + "position : " + position); }
これでアニメーションが表示されましたが、最初の追加(初期化)後にリストは表示されませんでした。 次の操作の後、要素が追加され、すべてが表示されましたが。 要素を追加する方法に別のハックを作成しました。 彼が何をしているのかはっきりしていることを願っています
if ( adapter.getItemCount() == 1 ) { adapter.notifyDataSetChanged(); }
記事「
RecyclerView LayoutManagerの構築-パート1」には、一見全体的な問題に対する解決策がありますが、私にはうまくいきませんでした。 サポートライブラリのバージョンを更新する必要があるか、SDKが必要な場合があります。 知りません
これは、実際にLayoutManagerをコンパイルするために必要な唯一のオーバーライドです。 実装は非常に簡単で、Recyclerから返されるすべての子ビューにデフォルトで適用するRecyclerView.LayoutParamsの新しいインスタンスを返すだけです。 これらのパラメーターは、getViewForPosition()から戻る前に各子に適用されます
@Override public RecyclerView.LayoutParams generateDefaultLayoutParams() { return new RecyclerView.LayoutParams( RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT); }
結果として、我々は「幽霊」アプローチを採用しています。これは適用する価値があります...
そのようなPopUpウィジェットは、プログラムメッセージの表示に役立ちますが。 進行状況のウィンドウの代わりに。 タイマーを使用すると、一定の時間後に、またはすぐにクリックして、トップメッセージを削除できます
結果は、影とアニメーションを含む断片化されたリストです。 画面の回転を正しく処理しました。 アプリケーションに簡単に統合できます。 唯一の些細なこと。 フラグメントのスタックを並べ替えた後、ユーザーがアプリケーションを操作したときに、ウィンドウが常に表示されるとは限りませんでした。 おそらく、一部のコールバックはUI Thread ... Solutionにありません。ハンドラーを介してフラグメントにアクセスします。
new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { mOverlayMessageFragment.addMessage(text); } });
そして、フラグメントの別の便利な機能:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
コードの変更は次のとおりです。
PopUpFragment.java package net.appz.iconfounder.popupwidget.fragment; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.app.Fragment; import android.support.v7.widget.CardView; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import net.appz.iconfounder.R; import net.appz.iconfounder.popupwidget.adapter.RecyclerViewAdapter; import net.appz.iconfounder.popupwidget.model.Record; import java.util.ArrayList; import java.util.List; public class PopUpFragment extends Fragment{ private static final String ARG_TIMER_INTERVAL = "timer_interval"; private OnFragmentInteractionListener mListener; private HandlerPopUpMessages messageHandler; private int TIMER_INTERVAL_DEFAULT = 2000; private int timer_interval; private RecyclerViewAdapter adapter; private CardView cardView; private RecyclerView recyclerView; private List<Record> records = new ArrayList<Record>(); public static PopUpFragment newInstance(int timer_interval) { PopUpFragment fragment = new PopUpFragment(); Bundle args = new Bundle(); args.putInt(ARG_TIMER_INTERVAL, timer_interval); fragment.setArguments(args); return fragment; } public static PopUpFragment newInstance() { PopUpFragment fragment = new PopUpFragment(); return fragment; } public PopUpFragment() {
レイアウト <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" tools:context=".PopUpFragment"> <android.support.v7.widget.CardView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/cardView" card_view:cardCornerRadius="6dp" card_view:cardBackgroundColor="#84ffff"> <android.support.v7.widget.RecyclerView android:layout_width="400dp" android:layout_height="wrap_content" android:id="@+id/recyclerView" /> </android.support.v7.widget.CardView> </FrameLayout>
MainActivity.java ... mPopupWidget = (PopUpFragment) getSupportFragmentManager().findFragmentById(R.id.popup); if (DEBUG) Log.d(TAG, "onCreate() : mPopupWidget = " + mPopupWidget); if( mPopupWidget == null ){ getSupportFragmentManager().beginTransaction() .replace(R.id.popup, PopUpFragment.newInstance(), PopUpFragment.class.getSimpleName()) .commit(); } ...
RecyclerViewAdapter package com.renal128.demo.recyclerviewdemo.adapter; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import com.renal128.demo.recyclerviewdemo.R; import com.renal128.demo.recyclerviewdemo.model.Record; import java.util.List; public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private List<Record> records; public RecyclerViewAdapter(List<Record> records) { this.records = records; } public List<Record> getRecords() { return records; } @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerview_item, viewGroup, false); return new ViewHolder(v); } @Override public void onBindViewHolder(ViewHolder viewHolder, int i) { Record record = records.get(i); int iconResourceId = 0; switch (record.getType()) { case GREEN: iconResourceId = R.drawable.green_circle; break; case RED: iconResourceId = R.drawable.red_circle; break; case YELLOW: iconResourceId = R.drawable.yellow_circle; break; } viewHolder.icon.setImageResource(iconResourceId); viewHolder.name.setText(record.getName()); viewHolder.deleteButtonListener.setRecord(record); viewHolder.copyButtonListener.setRecord(record); } @Override public int getItemCount() { return records.size(); } private void copy(Record record) { int position = records.indexOf(record); Record copy = record.copy(); records.add(position + 1, copy);
元のコードは次の
とおりです:
github.com/renal128/RecyclerViewDemoハンドラーとタイマーの実装:
github.com/app-z/PopUpWidgetGoogle Playからその仕組みを確認できます:
play.google.com/store/apps/details?id=net.appz.iconfounder