首页 > 解决方案 > 使用 ConstraintLayout 的片段 onCreateView 不遵守 ConstraintSet

问题描述

我在不使用 XML 文件的情况下以编程方式构建我的所有 UI。我的主 UI 类继承自 AppCompatActivity() 并使用一个名为 ClientFragment 的片段作为 UI 的主要部分

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val layout = ConstraintLayout(this)
    layout.id = R.id.constraint_layout_main_activity
    layout.setBackgroundColor(Color.GREEN)
    setContentView(layout)

    if (savedInstanceState == null) {
        supportFragmentManager.beginTransaction()
            .add(R.id.constraint_layout_main_activity, ClientFragment(), "CLIENT_FRAGMENT")
            .commit()
    }
}

}

我有一个名为 BaseGridFragment 的抽象基 Fragment 类,我的所有片段都将实现它。BaseGridFragment 包含显示在屏幕上的所有小部件。

abstract class BaseGridFragment : Fragment() {
protected lateinit var progressBar: ProgressBar
protected lateinit var swipeRefreshLayout: SwipeRefreshLayout
private lateinit var recyclerView: RecyclerView
protected lateinit var recyclerAdapter: RecyclerAdapter
private lateinit var constraintSet: ConstraintSet
private lateinit var gridLayoutManager: GridLayoutManager
protected lateinit var searchView: SearchView

protected val requestSender = RequestSender()

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    context?.let { context ->
        val layout = ConstraintLayout(context)
        layout.id = R.id.constraint_layout_base_fragment
        layout.setBackgroundColor(Color.BLUE)

        progressBar = ProgressBar(context)
        progressBar.id = R.id.progress_bar_all_loading

        searchView = SearchView(context)
        searchView.id = R.id.search_view_base_fragment
        searchView.queryHint = "Search"
        searchView.isIconifiedByDefault = false

        gridLayoutManager = GridLayoutManager(context, 2)
        recyclerAdapter =
            RecyclerAdapter {
                onItemClicked(it)
            }

        recyclerView = RecyclerView(context)
        recyclerView.id = R.id.recycler_view_all_list_of_items
        recyclerView.adapter = recyclerAdapter
        recyclerView.layoutManager = gridLayoutManager

        swipeRefreshLayout = SwipeRefreshLayout(context)
        swipeRefreshLayout.id = R.id.swipe_refresh_layout_all_item_list_container
        swipeRefreshLayout.addView(recyclerView)
        swipeRefreshLayout.setOnRefreshListener { fetchData() }

        layout.addView(swipeRefreshLayout)
        layout.addView(progressBar)
        layout.addView(searchView)

        constraintSet = ConstraintSet()
        constraintSet.constrainWidth(R.id.search_view_base_fragment, ConstraintSet.PARENT_ID)
        constraintSet.connect(
            R.id.search_view_base_fragment,
            ConstraintSet.LEFT,
            ConstraintSet.PARENT_ID,
            ConstraintSet.LEFT
        )
        constraintSet.connect(
            R.id.search_view_base_fragment,
            ConstraintSet.TOP,
            ConstraintSet.PARENT_ID,
            ConstraintSet.TOP
        )
        constraintSet.connect(
            R.id.search_view_base_fragment,
            ConstraintSet.RIGHT,
            ConstraintSet.PARENT_ID,
            ConstraintSet.RIGHT
        )
        constraintSet.constrainWidth(R.id.search_view_base_fragment, ConstraintSet.PARENT_ID)
        constraintSet.constrainHeight(
            R.id.search_view_base_fragment,
            ConstraintSet.WRAP_CONTENT
        )
        constraintSet.constrainWidth(R.id.progress_bar_all_loading, ConstraintSet.WRAP_CONTENT)
        constraintSet.constrainHeight(R.id.progress_bar_all_loading, ConstraintSet.WRAP_CONTENT)
        constraintSet.centerHorizontally(R.id.progress_bar_all_loading, ConstraintSet.PARENT_ID)
        constraintSet.centerVertically(R.id.progress_bar_all_loading, ConstraintSet.PARENT_ID)

        constraintSet.connect(
            R.id.swipe_refresh_layout_all_item_list_container,
            ConstraintSet.TOP,
            R.id.search_view_base_fragment,
            ConstraintSet.BOTTOM
        )
        constraintSet.constrainWidth(
            R.id.swipe_refresh_layout_all_item_list_container,
            ConstraintSet.WRAP_CONTENT
        )
        constraintSet.connect(
            R.id.swipe_refresh_layout_all_item_list_container,
            ConstraintSet.BOTTOM,
            ConstraintSet.PARENT_ID,
            ConstraintSet.BOTTOM
        )

        constraintSet.applyTo(layout)

        searchView.visibility = View.GONE
        swipeRefreshLayout.visibility = View.GONE

        return layout
    }

    return null
}

abstract fun fetchData()

abstract fun onItemClicked(item: RecyclerAdapter.Item)

}

ClientFragment 实现了这个抽象类,它负责获取将填充所有小部件的数据。

class ClientFragment : BaseGridFragment() {
override fun onAttach(context: Context) {
    super.onAttach(context)

    (activity as AppCompatActivity).supportActionBar?.title = "Clients"

    fetchData()
}

override fun fetchData() {
    requestSender.sendRequest(context!!, "/api/clients", JSONArray()) { result ->
        when (result) {
            is RequestSender.RequestSenderResult.Success -> {
                if (result.data.length() != 1) {
                    return@sendRequest
                }

                recyclerAdapter.items.clear()

                val clients = result.data[0] as JSONArray
                for (i in 0 until clients.length()) {
                    val client = clients.get(i) as JSONArray
                    if (client.length() != 3) {
                        Log.e(
                            "ClientActivity",
                            "Invalid client data length while parsing client response. Skipping client."
                        )
                        continue
                    }

                    recyclerAdapter.items.add(
                        RecyclerAdapter.Item(
                            client[0] as String,
                            client[1] as String,
                            URL(client[2] as String)
                        )
                    )
                }

                // Clearing data and calling notifyDataSetChanged() is relatively expensive. If
                // number of items returned causes UI smoothness problem consider notifyDataSetChanged
                // for specific indexes.
                activity!!.runOnUiThread { recyclerAdapter.notifyDataSetChanged() }

                progressBar.visibility = View.GONE
                swipeRefreshLayout.visibility = View.VISIBLE
                searchView.visibility = View.VISIBLE
                swipeRefreshLayout.isRefreshing = false
            }
            is RequestSender.RequestSenderResult.Error -> {
                val alert = AlertDialog.Builder(context!!)
                alert.setPositiveButton("OK", null)
                when (result.error) {
                    is RequestSender.RequestSenderError.NetworkError -> {
                        alert.setTitle("Network Error")
                        alert.setMessage(result.error.description)
                    }
                    is RequestSender.RequestSenderError.ClientError -> {
                        alert.setTitle("Request Error")
                        alert.setMessage(result.error.description)
                    }
                    is RequestSender.RequestSenderError.InternalServerError -> {
                        alert.setTitle("Server Error")
                        alert.setMessage(result.error.description)
                    }
                    is RequestSender.RequestSenderError.UnrecognizedNetworkStatusCode -> {
                        alert.setTitle("Network Error")
                        alert.setMessage(result.error.description)
                    }
                    is RequestSender.RequestSenderError.UnableToDeserializeResponse -> {
                        alert.setTitle("Deserialization Error")
                        alert.setMessage(result.error.description)
                    }
                }
                activity!!.runOnUiThread {
                    alert.create().show()
                    progressBar.visibility = View.GONE
                    swipeRefreshLayout.visibility = View.VISIBLE
                    swipeRefreshLayout.isRefreshing = false
                }
            }
        }
    }
}

override fun onItemClicked(item: RecyclerAdapter.Item) {
    (activity as MainActivity).onClientClicked(item)
}

}

当我运行此代码时,BaseGridFragment 中的 ConstraintSet 无法正常工作。当应用程序第一次启动时,会有一个网络调用来获取一些数据。在获取数据时,我在屏幕中间显示了一个微调器。下载数据后,UI 更改为顶部显示搜索栏,下方显示 CardView。

MainActivity 中的根视图是一个 ConstraintLayout,BaseGridFragment 中的主视图也是如此,它包含所有其他小部件。我对两个主要视图进行了颜色编码:MainActivity 中的布局为绿色,BaseGridFragment 中的布局为蓝色。

在此处输入图像描述在此处输入图像描述

根据我对 supportFragmentManager 工作原理的理解,以下代码应该使 BaseGridFragment 中的布局成为 MainActivity 中布局的直接子级。因此,我在 BaseGridFragment 中定义的引用 ConstraintSet.PARENT_ID 的任何约束都应该起作用:

supportFragmentManager.beginTransaction()
            .add(R.id.constraint_layout_main_activity, ClientFragment(), "CLIENT_FRAGMENT")
            .commit(

正如您从屏幕截图中看到的那样,蓝色区域并未像应有的那样扩展为绿色:微调器未居中,并且 CardView 根本没有显示在搜索栏下方。如果我重构我的代码并且我摆脱了所有片段并简单地将我的 BaseGridFragment 布局中的所有内容放入我的活动中,那么一切都会正常工作,所以一定有一些关于我在这里遗漏的片段的内容。

我已经寻找解决方案,但我遇到的所有示例都使用 XML 来定义它们的 UI,然后在 onCreateView() 中使用充气器。

标签: androidandroid-layoutandroid-fragmentskotlinandroid-constraintlayout

解决方案


我没有看到您将布局参数设置为主布局

layout.layoutParams = ConstraintLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)


推荐阅读