使用RecyclerView添加到GridLayout的动态按钮会相互添加

我想将动态按钮添加到网格布局,如下所示。 每个按钮一次添加一个 ,并且布局必须自行更新以使按钮成行。 我的问题是,当添加一个新按钮时,它会出现在添加的最后一个按钮的顶部,而不是以行格式放置。 在下图中,我展示了我想要的和当前正在发生的事情,标有数字的方块是我的按钮。 在我所拥有的图像中,我在布局中添加了6个按钮,但它们都是相互叠加的。

在此处输入图像描述

我已经四处查看了如何做到这一点,并建议我使用带有GridLayoutManagerRecyclerView 。 我已将此添加到我的代码中,但就像我说的问题是,当我添加一个按钮,然后如果我添加另一个按钮,第二个添加到第一个按钮之上。 如果我的按钮是预设的,我的工作原理,但不适用于动态按钮。

这是我的代码:

Main Fragment启动RecyclerView 。 我有另一个活动启动createButton()方法并传递drawableString 。 这些drawable和字符串将根据用户的操作一次一个地传递给此方法,并一次创建一个图像按钮。

 public class MyFragment extends Fragment { private GridLayoutManager lLayout; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } // onCreateView @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.my_fragment, container, false); // Get screen size to have different layouts for phone and tablet int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; String toastMsg; switch (screenSize) { case Configuration.SCREENLAYOUT_SIZE_LARGE: toastMsg = "Large screen"; Log.d("tag_name", "Large screen"); break; case Configuration.SCREENLAYOUT_SIZE_NORMAL: toastMsg = "Normal screen"; Log.d("tag_name", "Normal screen"); break; case Configuration.SCREENLAYOUT_SIZE_SMALL: toastMsg = "Small screen"; Log.d("tag_name", "Small screen"); break; default: toastMsg = "Screen size is neither large, normal or small"; Log.d("tag_name", "Screen size is not large, normal, or small"); } Toast.makeText(getActivity(), toastMsg, Toast.LENGTH_LONG).show(); // Create an empty list to initialize the adapter (or else get nullPointerException error) List myList = new ArrayList(); // 3 rows for tablet // 2 rows for phone if (screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE || screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE) { lLayout = new GridLayoutManager(getActivity(), 3, GridLayoutManager.HORIZONTAL, false); } else lLayout = new GridLayoutManager(getActivity(), 2, GridLayoutManager.HORIZONTAL, false); RecyclerView rView = (RecyclerView) view.findViewById(R.id.recycler_view); rView.setHasFixedSize(true); rView.setLayoutManager(lLayout); RecyclerViewAdapter rcAdapter = new RecyclerViewAdapter(getActivity(), myList); rView.setAdapter(rcAdapter); return view; } private List getAllItemList(String applicationName, Drawable app_drawable) { List allItems = new ArrayList(); allItems.add(new ItemObject(applicationName, app_drawable)); return allItems; } public void createButton(Drawable d, String appName) { List rowListItem = getAllItemList(appName, d); lLayout = new GridLayoutManager(getActivity(), 2, GridLayoutManager.HORIZONTAL, false); RecyclerView rView = (RecyclerView) getView().findViewById(R.id.recycler_view); rView.setHasFixedSize(true); rView.setLayoutManager(lLayout); RecyclerViewAdapter rcAdapter = new RecyclerViewAdapter(getActivity(), rowListItem); rView.setAdapter(rcAdapter); } } 

我的布局(my_fragment):

        

编辑

这是我对MyFragment的更新代码现在我在添加第二个ImageButton时收到nullPointerException

 public class MyFragment extends Fragment { private GridLayoutManager lLayout; private ButtonCreator bc; RecyclerViewAdapter rcAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); List myList = new ArrayList(); rcAdapter = new RecyclerViewAdapter(getActivity(),myList); } // onCreateView @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.home_fragment, container, false); // Get screen size so we can have different layouts for phone and tablet int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; String toastMsg; switch(screenSize) { case Configuration.SCREENLAYOUT_SIZE_LARGE: toastMsg = "Large screen"; Log.d("tag_name", "Large screen"); break; case Configuration.SCREENLAYOUT_SIZE_NORMAL: toastMsg = "Normal screen"; Log.d("tag_name", "Normal screen"); break; case Configuration.SCREENLAYOUT_SIZE_SMALL: toastMsg = "Small screen"; Log.d("tag_name", "Small screen"); break; default: toastMsg = "Screen size is neither large, normal or small"; Log.d("tag_name", "Screen size is not large, normal, or small"); } Toast.makeText(getActivity(), toastMsg, Toast.LENGTH_LONG).show(); // Create an empty list to initialize the adapter (or else get nullPointerException error) if (screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE || screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE) { lLayout = new GridLayoutManager(getActivity(), 3, GridLayoutManager.HORIZONTAL, false); } else lLayout = new GridLayoutManager(getActivity(), 2, GridLayoutManager.HORIZONTAL, false); RecyclerView rView = (RecyclerView)view.findViewById(R.id.recycler_view); rView.setHasFixedSize(true); rView.setLayoutManager(lLayout); rView.setAdapter(rcAdapter); return view; } private List getAllItemList(String applicationName, Drawable app_drawable){ List allItems = new ArrayList(); allItems.add(new ItemObject(applicationName, app_drawable)); return allItems; } public void createButton (Drawable d, String appName){ rcAdapter.addItem(new ItemObject(appName , d)); } // In order to get the view of HomeFragment correctly public interface ButtonCreator{ void buttonCreator(Drawable d, String string); } @Override public void onAttach(Activity activity) { super.onAttach(activity); bc = (ButtonCreator) activity; } } 

我如何在MyFragment中调用createButton方法(在runOnUiThread方法中调用)

 public class MainActivity extends FragmentActivity implements MyFragment.ButtonCreator { private Thread repeatTaskThread; private byte[] byteArray; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.replace(R.id.container, new MyFragment()).commit(); } public void buttonCreator(Drawable d,String a) { MyFragment homeFragment = (MyFragment)getSupportFragmentManager().getFragments().get(1); homeFragment.createButton(d, a); } public void RepeatTask(){ repeatTaskThread = new Thread() { public void run() { while (true) { try { System.out.println("TRY_1"); Socket socket = new Socket("192.168.0.26", 5050); // Get data sent through socket DataInputStream DIS = new DataInputStream(socket.getInputStream()); System.out.println("DataInputStream Started"); // read data that got sent final String applicationName = DIS.readUTF(); // read array data for bitmap int len = DIS.readInt(); byte[] data = new byte[len]; DIS.readFully(data, 0, data.length); Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); final Drawable d = new BitmapDrawable(getResources(), bitmap); runOnUiThread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub Log.d("tag_name", "Try_2"); buttonCreator(d,applicationName); } }); socket.close(); } catch (Exception e) { System.out.println("Exception is " + e.toString()); } try { // Sleep for 5 seconds Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } } } ; }; repeatTaskThread.start(); } } 

我的应用程序在创建第二个按钮时崩溃时出现错误:

 05-11 17:05:25.275 14257-14257/it.anddev.bradipao.janus E/RecyclerView: No adapter attached; skipping layout 05-11 17:05:25.275 14257-14257/it.anddev.bradipao.janus D/AndroidRuntime: Shutting down VM 05-11 17:05:25.275 14257-14257/it.anddev.bradipao.janus W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x437c0160) 05-11 17:05:25.305 14257-14257/it.anddev.bradipao.janus E/AndroidRuntime: FATAL EXCEPTION: main Process: it.anddev.bradipao.janus, PID: 14257 java.lang.NullPointerException at android.support.v7.widget.RecyclerView.computeHorizontalScrollRange(RecyclerView.java:1518) at android.view.View.onDrawScrollBars(View.java:12248) at android.view.View.draw(View.java:14794) at android.support.v7.widget.RecyclerView.draw(RecyclerView.java:3171) at android.view.View.getDisplayList(View.java:13655) at android.view.View.getDisplayList(View.java:13697) at android.view.View.draw(View.java:14505) at android.view.ViewGroup.drawChild(ViewGroup.java:3134) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2971) at android.view.View.getDisplayList(View.java:13650) at android.view.View.getDisplayList(View.java:13697) at android.view.View.draw(View.java:14505) at android.view.ViewGroup.drawChild(ViewGroup.java:3134) at android.support.v7.widget.RecyclerView.drawChild(RecyclerView.java:3693) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2971) at android.view.View.draw(View.java:14791) at android.support.v7.widget.RecyclerView.draw(RecyclerView.java:3171) at android.view.View.getDisplayList(View.java:13655) at android.view.View.getDisplayList(View.java:13697) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3108) at android.view.View.getDisplayList(View.java:13593) at android.view.View.getDisplayList(View.java:13697) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3108) at android.view.View.getDisplayList(View.java:13593) at android.view.View.getDisplayList(View.java:13697) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3108) at android.view.View.getDisplayList(View.java:13593) at android.view.View.getDisplayList(View.java:13697) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3108) at android.view.View.getDisplayList(View.java:13593) at android.view.View.getDisplayList(View.java:13697) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3108) at android.view.View.getDisplayList(View.java:13593) at android.view.View.getDisplayList(View.java:13697) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3108) at android.view.View.getDisplayList(View.java:13593) at android.view.View.getDisplayList(View.java:13697) at android.view.HardwareRenderer$GlRenderer.buildDisplayList(HardwareRenderer.java:1570) at android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:1449) at android.view.ViewRootImpl.draw(ViewRootImpl.java:2476) at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2300) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1929) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1043) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5885) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:771) at android.view.Choreographer.doCallbacks(Choreographer.java:574) at android.view.Choreographer.doFrame(Choreographer.java:544) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:757) at android.os.Handler.handleCallback(Handler.java:733) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:149) at android.app.ActivityThread.main(ActivityThread.java:5257) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:788) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:604) at dalvik.system.NativeStart.main(Native Method) 

做这样的事情

 public class MyFragment extends Fragment { private GridLayoutManager lLayout; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } // onCreateView @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.my_fragment, container, false); // Get screen size to have different layouts for phone and tablet int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; String toastMsg; switch (screenSize) { case Configuration.SCREENLAYOUT_SIZE_LARGE: toastMsg = "Large screen"; Log.d("tag_name", "Large screen"); break; case Configuration.SCREENLAYOUT_SIZE_NORMAL: toastMsg = "Normal screen"; Log.d("tag_name", "Normal screen"); break; case Configuration.SCREENLAYOUT_SIZE_SMALL: toastMsg = "Small screen"; Log.d("tag_name", "Small screen"); break; default: toastMsg = "Screen size is neither large, normal or small"; Log.d("tag_name", "Screen size is not large, normal, or small"); } Toast.makeText(getActivity(), toastMsg, Toast.LENGTH_LONG).show(); // Create an empty list to initialize the adapter (or else get nullPointerException error) // 3 rows for tablet // 2 rows for phone if (screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE || screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE) { lLayout = new GridLayoutManager(getActivity(), 3, GridLayoutManager.HORIZONTAL, false); } else lLayout = new GridLayoutManager(getActivity(), 2, GridLayoutManager.HORIZONTAL, false); RecyclerView rView = (RecyclerView) view.findViewById(R.id.recycler_view); rView.setHasFixedSize(true); rView.setLayoutManager(lLayout); rView.setAdapter(rcAdapter); return view; } RecyclerViewAdapter rcAdapter; private List getAllItemList(String applicationName, Drawable app_drawable) { List allItems = new ArrayList(); allItems.add(new ItemObject(applicationName, app_drawable)); return allItems; } public void createButton(Drawable d, String appName) { rcAdapter.addItem(new ItemObject(appName , d)) } } 

并在适配器中添加此方法

 public void addItem(int position) { list.add(position); notifyItemInserted(position); notifyItemRangeChanged(position, list.size()); } 

编辑

要防止出现警告,请尝试将适配器初始化移至onCreate

 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); List myList = new ArrayList(); rcAdapter = new RecyclerViewAdapter(getActivity(), myList); } 

创建按钮时,您只需要做两件事:

  1. 更新支持RecyclerView适配器的数据结构
  2. 使用适配器的notify...()方法之一通知适配器数据已更改(有几个 ,使用最适用的一个)

您当前的代码似乎没有做这些事情。 您也不需要创建或设置任何新适配器或布局管理器。

在您的情况下,您将添加一个元素到您的适配器类保持的列表,然后可能调用具有适当位置的notifyItemInserted() 。 您可以通过向适配器添加方法来同时执行这两项操作:

 public void addItem(ItemObject item) { mList.add(item); notifyItemInserted(mList.size() - 1); } 

不相关:在Android上,您可以使用资源解析系统来确定要显示的列数。

创建src/main/res/values/integers.xml并添加:

  2  

创建src/main/res/values-large/integers.xml并添加:

  3  

请注意-large ,它将通知系统此大小及以上的屏幕应使用此备用资源值。 现在在你的片段中你可以简单地写:

 int columnCount = getResources().getInteger(R.integer.grid_column_count); GridLayoutManager layout = new GridLayoutManager(getActivity(), columnCount, GridLayoutManager.HORIZONTAL, false); 

我之所以提到这一点,是因为屏幕大小桶( -large )可能不是确定列数的最佳方法。 您可能更适合使用屏幕宽度限定符(例如-w600dp ),如果设备旋转,则会更新(而大小桶在该方案中不会更改)。 您可以在此处阅读有关资源限定符的更多信息