preface
This is the fourth article in the series, and here are the first three:
A general overview of playing Android from 0 to 1
2, play Android from 0 to 1 project home page
3. Play Android home page frame building from 0 to 1.
As usual, put the Github address and apK download address.
Apk download address: www.pgyer.com/llj2
Github address: github.com/zhujiang521…
The cause of
Why write this article? I feel like I’ve come back to where I started.
Among the comments on the first article was the following:
In the first article, we built BaseActivity and BaseFragment. If you don’t know, read the first article: an overview of playing Android from 0 to 1. Some common methods are extracted and LCE operations, such as display error, load failure, load content, network error, etc., are placed in BaseActivity and BaseFragment.
I thought it would be convenient to include LCE’s pages directly in pages requiring different statuses, but after seeing the comments of this friend named Alienzh, I realized that IT was really bad to write this way, because many pages in this small project need LCE. Each page needs to include, which is a bit of a mistake when writing this small project. Each time you add a FrameLayout to the LCE page, you create a layer of nested layouts, such as the following:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.project.list.ProjectListFragment">
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/offListSmartRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/offListRecycleView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
<include
layout="@layout/layout_lce"/>
</FrameLayout>
Copy the code
Originally, the layout of the first floor was directly made into such, looking not beautiful. So think according to the idea of this elder brothers to make a wave of try!
To solve
BaseActivity increase LCE
A method called addContentView does not remove previously added UI components. Instead, it accumulates the newly added space. Do as soon as you say so:
val view = View.inflate(this, R.layout.layout_lce, null)
val params = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
params.setMargins(0,
ConvertUtils.dp2px(if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) 70f else 55f),0.0
)
addContentView(view, params)
Copy the code
The layout of the LCE is synchronized in directly through the View, and the height of the TitleBar is set aside according to the vertical and horizontal panels, otherwise the header layout will not appear.
The following is very simple, as before:
loading = view.findViewById(R.id.loading)
noContentView = view.findViewById(R.id.noContentView)
badNetworkView = view.findViewById(R.id.badNetworkView)
loadErrorView = view.findViewById(R.id.loadErrorView)
loadFinished()
Copy the code
FindViewById is the same as before, except that you findViewById through the View that is just inflate, and don’t forget to add loadFinished() at the end, because the default is to display the layout normally.
OK! It’s very simple, but it saves a lot of work, and it’s used in a lot of places.
BaseFragment increase LCE
Fragment is the same as an Activity. I’m just going to use addContentView. That’s what I thought at first, but then I realized I was wrong…
Why was that wrong? Check out the Fragment, there is no such method (maybe there is, but I haven’t found it, let me know in the comments section, thank you very much).
This is… Do how?
Let’s take a look at how to load a layout when writing fragments:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup? , savedInstanceState:Bundle?).: View? {
return inflater.inflate(getLayoutId(), container, false)}Copy the code
GetLayoutId () above is an abstract method that gets the layout of the subclass.
See? You just return a View that is inflate out, so that’s fine.
Think again, what is our purpose, is to add LCE layout, in the above layout file how do we operate? The View already knows what the LCE layout is, so let’s create a FrameLayout code to wrap it. Do as soon as you say so:
val frameLayout = FrameLayout(context!!)
Copy the code
The LCE layout is synchronized in with the View:
val lce = View.inflate(context, R.layout.layout_lce, null)
val params = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
val isPort = resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
params.setMargins(0,ConvertUtils.dp2px(if (isPort) 70f else 55f),0.0)
lce.layoutParams = params
Copy the code
Now we have the View of LCE, FrameLayout is created, the original layout is abstracted, everything is ready, we just need to add these two layouts, let’s look at the final code:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View? {
val frameLayout = FrameLayout(context!!)
val lce = View.inflate(context, R.layout.layout_lce, null)
val params = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
val isPort = resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
params.setMargins(0,ConvertUtils.dp2px(if (isPort) 70f else 55f),0.0)
lce.layoutParams = params
val content = inflater.inflate(getLayoutId(), container, false)
frameLayout.addView(content)
frameLayout.addView(lce)
onCreateView(lce)
return frameLayout
}
Copy the code
That’s it! Does it feel like an Epiphany?! When adding frameLayout to addView, it is important to pay attention to the order in which the frameLayout should be added. I have suffered from a mistake in the order before, and as a result, the click time of LCE layout cannot be used. Later, I realized that I should put LCE on top, that is, addView at the back.
Continue to explore
The above BaseActivity and BaseFragment classes extract the LCE layout into the parent class, which relieves some of the subclasses’ burden, but still feels like something is wrong.
viewModel.getData().observe(this, Observer {
if (it.isSuccess) {
loadFinished()
val projectTree = it.getOrNull()
if(projectTree ! =null) {
// Perform the operation
} else {
showLoadErrorView()
}
} else {
showBadNetworkView(View.OnClickListener { initData() })
}
})
Copy the code
Basically, the LiveData used in ViewModel is the same process, so you can also extract ah, before I do not know how to extract, but later thought, write a method, pass LiveData in, in the callback out of the subclass for the corresponding operation!
First edition optimization
Let’s look at the first version of the code:
fun <T> setDataStatus(dataLiveData: LiveData<Result<T> >){
dataLiveData.observe(this) {if (it.isSuccess) {
val articleList = it.getOrNull()
if(articleList ! =null) {
loadFinished()
setData(articleList)
} else {
showLoadErrorView()
}
} else {
showBadNetworkView { initData() }
}
}
}
protected open fun <T> setData(data: T){}Copy the code
Let’s talk briefly about what the above code means. SetData: setData: setData: setData: setData: setData: setData: setData: setData: setData: setData: setData
setDataStatus(viewModel.projectTreeLiveData)
Copy the code
Just throw LiveData in, and then override the setData method next:
override fun <T> setData(data: T){
data as List<ProjectClassify>
// Perform the corresponding operation
}
Copy the code
Isn’t that easy, but it feels like something’s wrong and I need a strong turn? Should be directly access to the corresponding type of ah! At that time, I felt like I had reached a dead end. There were so many ways to go wrong that I had to get stuck. I thought of Kotlin’s generic realizations, inline functions, and Crossinline, but it didn’t matter.
Second Edition optimization
Sometimes writing code is like this, the idea of suddenly fixed can not come out! When it came to accepting another interface callback in the method, there was a second version:
fun <T> setDataStatus(dataLiveData: LiveData<Result<T>>, onDataStatus: DataStatusListener<T>) {
dataLiveData.observe(this) {
if (it.isSuccess) {
val dataList = it.getOrNull()
if(dataList ! =null) {
loadFinished()
onDataStatus.onDataStatus(dataList)
} else {
showLoadErrorView()
}
} else {
showBadNetworkView { initData() }
}
}
}
interface DataStatusListener<T> {
fun onDataStatus(t: T)
}
Copy the code
This is not ok! Let’s take a look at the changes:
setDataStatus(dd.getDataLiveData(), collect -> {
// Perform the corresponding operation
});
Copy the code
The Third Edition of Exploration
It just adds an excuse to the perfect solution to the problem of needing a strong turn. No! This is Kotlin, no excuse to call back, Kotlin can kill, high order function is not to do this thing! Brain really watt off!
fun <T> setDataStatus(dataLiveData: LiveData<Result<T>>, onDataStatus: (T) - >Unit) {
dataLiveData.observe(this) {
if (it.isSuccess) {
val dataList = it.getOrNull()
if(dataList ! =null) {
loadFinished()
onDataStatus(dataList)
} else {
showLoadErrorView()
}
} else {
showBadNetworkView { initData() }
}
}
}
Copy the code
So write not sweet 😂! With all the bells and whistles! What excuse do you need? No more!
Problems encountered
In this project, I accessed Bugly of Tencent to check the Crash in use, and found that there was always a problem:
Question why
I know which piece of code went wrong, but I just don’t know how to change it. Baidu and Google don’t know how long they have no clue. Let me show you the code that went wrong:
protected open fun fragmentManger(position: Int) {
mViewModel.setPage(position)
valtargetFg: Fragment = mFragments!! [position]valtransaction = mFragmentManager!! .beginTransaction()if(currentFragment ! =null) {
transaction.hide(currentFragment!!)
}
if(! targetFg.isAdded) { transaction.add(R.id.flHomeFragment, targetFg).commit() }else {
// There is an error
transaction.show(targetFg).commit()
}
currentFragment = targetFg
}
Copy the code
It was a very simple piece of code, just a Fragment was switched, so the error was always reported. You can also go to Baidu at will. This problem made me sick at that time, I always felt it should be a small mistake, but I couldn’t find the error!
It’s a disgusting feeling, but it happens all the time. I won’t go into the details of the process. It was hard, but the solution and the reason are very simple…
Take a look at the details:
Because the HomePageFragment is already attached to the FragmentManager, it cannot be attached again. Simple question, but why?? Why not? There are no other mistakes!
In the end, the culprit was my use of singletons…
object FragmentFactory {
private val mHomeFragment: HomePageFragment by lazy { HomePageFragment.newInstance() }
private val mProjectFragment: ProjectFragment by lazy { ProjectFragment.newInstance() }
private val mObjectListFragment: OfficialAccountsFragment by lazy { OfficialAccountsFragment.newInstance() }
private val mProfileFragment: ProfileFragment by lazy { ProfileFragment.newInstance() }
fun getCurrentFragment(index: Int): Fragment? {
return when (index) {
0 -> mHomeFragment
1 -> mProjectFragment
2 -> mObjectListFragment
3 -> mProfileFragment
else -> null}}}Copy the code
The singleton that was created so that fragments could be reused without having to re-create it, and everything went wrong because of it! Problems caused by life cycle inconsistency caused by singletons! It seems that in the future singleton also dare not blind use! Think it through.
The solution
The solution is simply to place the Fragment in the space and keep the same life cycle. There is no code attached here. Wanted to see can go to a lot of the download code: com. What zj had. Play. The main. BaseHomeBottomTabWidget.
conclusion
I have also written a lot and said a lot in a muddle. This article did not continue to write this small project, but looked back to see whether it should be written this way. I feel more useful than the previous several articles.
Ability is general, the level is limited, to everyone helpful words don’t forget three even, have Github account to help point a Star, grateful!
That’s it, see you next time!!