目前大多的app主页面构成是由导航栏(底部或顶部)+Fragments,例如这样
或这样
从它们展示的效果上来分析,fragment在首次显示的时候加载了数据,再次显示的时候数据并没有刷新,有可能是FragmentTransaction的show、hide操作(估计大厂应该不会这么干,反正我不会),也可能是attach、detach操作,不太可能的是add、remove、replace操作,后面详细解释。
但这不是重点,我要讲的重点是“kotlin自定义view以及管理fragments”。先谈谈管理,多个fragment放到list ?用FragmentManager,哎!这个靠点谱。那怎么和导航栏关联呢?这是问题一;再谈谈kotlin自定义view,关键有以下几点:构造方法、泛型、变量到底是private还是public,这是问题二;
当界面有多个fragment的时候,一般采用懒加载的方式,这样的好处就是不必一开始耗费内存和性能去加载一些可能永远都不会用到的东西,我们需要利用最短的时间显示内容给用户。
切换fragment的时候,将换出的fragment移出视图并放入FragmentManager的栈中,当再次加载它的时候利用tag将其从栈中找出并加入视图,这就避免了重复创建fragment。
因此tag显得尤为重要这也是自定义view的主要功能,次要功能就是自定义展示,专业一点有一个名词可以概括就是“解耦”。
操作流程
当用户选中自定义view(以下用TabItem表示),通过回调传递tag给控制视图(Activity),接收到tag后先查找FragmentManager是否有实例化,否则你懂的,然后添加到视图。
java自定义view是这样的
继承所需的父类,然后必须要重写构造方法,因为不同的构造方法对应不同的场景,如果对应的构造方法少了就会无法实例化以至于Exception,kotlin的思路是一致的,但道路。。。
kotlin的构造方法分为primary和second,所有的second都必须继承自primary,这里直接抛一个我的总结primary要用参数最多的(这是两个小时的汗水)。
这里加入了泛型用来给TabItem指定具体的fragment
show me the code
TabItem XML
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@string/app_name"
android:textColor="#555555"
android:textSize="13sp" />
merge>
TabItem最初继承的父类是FrameLayout而XML布局却用的LinearLayout做根标签,可以通过截图看到猫腻。后来在kotlin崎岖的道路上惊鸿一瞥看到了性能负担。于是TabItem改继承自LinearLayout,XML布局改用merge做根标签。前后对比会发现在视图结构上就会少一个FrameLayout层级,视图的层级越少绘制所消耗的性能就越少。但不要忘了在TabItem中设置
orientation = VERTICAL
gravity = Gravity.CENTER
setPadding(8, 8, 8, 8)
TabItem kotlin
class TabItem<T : BaseFragment>(context: Context, attrs: AttributeSet?, defStyleAttr: Int, fragmentClass: Class<T>?) : LinearLayout(context, attrs, defStyleAttr) {
private var tip: String? = null
private var selectColor: Int
private var unselectColor: Int
private var selectIcon: Drawable? = null
private var unselectIcon: Drawable? = null
private var isSelect = false
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context) : this(context, null)
constructor(context: Context, fragmentClass: Class<T>) : this(context, null, 0, fragmentClass)
init {
LayoutInflater.from(context).inflate(R.layout.view_tabitem, this, true)
val typeArray = context.obtainStyledAttributes(attrs, R.styleable.TabItem)
tip = typeArray?.getString(R.styleable.TabItem_tip)
selectColor = typeArray.getColor(R.styleable.TabItem_selectColor, Color.BLACK)
unselectColor = typeArray.getColor(R.styleable.TabItem_unselectColor, ContextCompat.getColor(context, R.color.gray))
selectIcon = typeArray?.getDrawable(R.styleable.TabItem_selectIcon)
unselectIcon = typeArray?.getDrawable(R.styleable.TabItem_unselectIcon)
typeArray?.recycle()
orientation = VERTICAL
gravity = Gravity.CENTER
setPadding(8, 8, 8, 8)
if (fragmentClass != null) {
tag = fragmentClass.name
}
show()
}
fun setTip(str: String) {
tip = str
show()
}
fun setColor(select: Int, unselect: Int) {
selectColor = select
unselectColor = unselect
show()
}
fun setIcon(select: Drawable, unselect: Drawable) {
selectIcon = select
unselectIcon = unselect
show()
}
fun isSelect(select: Boolean) {
isSelect = select
show()
}
private fun show() {
name.text = tip
if (isSelect) {
name.setTextColor(selectColor)
image.setImageDrawable(selectIcon)
} else {
name.setTextColor(unselectColor)
image.setImageDrawable(unselectIcon)
}
}
override fun setOnClickListener(l: OnClickListener?) {
super.setOnClickListener({ v ->
if (!isSelect) {
l?.onClick(v)
isSelect = !isSelect
show()
}
})
}
}
问题二的第一点已经讲了,来讲讲第二点。
T : BaseFragment表示T是BaseFragment的一种类型
Class表示BaseFragment的一个类
java:Class
if (fragmentClass != null) {
tag = fragmentClass.name
}
此段的逻辑是针对手动创建TabItem的,为tag赋类的全路径名
android:tag="com.ljf.eyepetizer.fragment.HomeFragment"
这个就不说了。
Activity XML
"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
"@+id/frame_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
"match_parent"
android:layout_height="0.5dp"
android:background="@color/gray" />
"@+id/bt_ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<com.ljf.eyepetizer.views.TabItem
android:id="@+id/homeTab"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="com.ljf.eyepetizer.fragment.HomeFragment"
app:selectIcon="@mipmap/home_selected"
app:tip="@string/home"
app:unselectIcon="@mipmap/home_normal" />
<com.ljf.eyepetizer.views.TabItem
android:id="@+id/findTab"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="com.ljf.eyepetizer.fragment.FindFragment"
app:selectIcon="@mipmap/find_selected"
app:tip="@string/find"
app:unselectIcon="@mipmap/find_normal" />
<com.ljf.eyepetizer.views.TabItem
android:id="@+id/hotTab"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="com.ljf.eyepetizer.fragment.NotifyFragment"
app:selectIcon="@mipmap/hot_selected"
app:tip="@string/hot"
app:unselectIcon="@mipmap/hot_normal" />
<com.ljf.eyepetizer.views.TabItem
android:id="@+id/mineTab"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="com.ljf.eyepetizer.fragment.MineFragment"
app:selectIcon="@mipmap/mine_selected"
app:tip="@string/mine"
app:unselectIcon="@mipmap/mine_normal" />
在布局中已经将TabItem与Fragment互相关联了
Activity kotlin
class MainActivity : BaseActivity() {
private lateinit var notifyTab: TabItem
private var currentFragmentTag: String? = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView()
}
private fun initView() {
homeTab.setOnClickListener(onTabItemClickListener)
findTab.setOnClickListener(onTabItemClickListener)
hotTab.setOnClickListener(onTabItemClickListener)
mineTab.setOnClickListener(onTabItemClickListener)
val params: LinearLayout.LayoutParams? = LinearLayout.LayoutParams(0, MATCH_PARENT)
params?.weight = 1f
notifyTab = TabItem(this, NotifyFragment::class.java)
notifyTab.setIcon(ContextCompat.getDrawable(this, R.mipmap.home_selected), ContextCompat.getDrawable(this, R.mipmap.home_normal))
notifyTab.setTip("动态添加")
notifyTab.setOnClickListener(onTabItemClickListener)
bt_ll.addView(notifyTab, params)
homeTab.callOnClick()
}
private var onTabItemClickListener = View.OnClickListener { v ->
clearTabItemsState()
showFragment(Class.forName(v.tag.toString()) as Class)
}
private fun clearTabItemsState() {
homeTab.isSelect(false)
findTab.isSelect(false)
hotTab.isSelect(false)
mineTab.isSelect(false)
notifyTab.isSelect(false)
}
private fun showFragment(fragmentClass: Class?) {
val trasaction = supportFragmentManager.beginTransaction()
val detachFragment = supportFragmentManager.findFragmentByTag(currentFragmentTag)
if (detachFragment != null) {
trasaction.detach(detachFragment)
}
var attachFragment = supportFragmentManager.findFragmentByTag(fragmentClass?.simpleName)
if (attachFragment == null) {
attachFragment = fragmentClass?.newInstance()
trasaction.add(R.id.frame_layout, attachFragment, fragmentClass?.simpleName)
} else {
trasaction.attach(attachFragment)
}
trasaction.commit()
currentFragmentTag = fragmentClass?.simpleName
}
}
Activity中设置了TabItem的单击事件,重点在showFragment里。
这里先讲讲FragmentTransaction的几种配套操作
- show、hide:操作过程1、初始化所有的Fragment(假设5个A、B、C、D、E),将其用变量或List保存旨在唯一标识的任何操作。2、将Fragments add操作到FragmentManager。3、根据用户操作hide不需要的Fragment,show需要显示的。
这样操作的优点就是所有初始化的Fragment都以加载完毕来回切换不会影响其生命周期;缺点就是我每次只会用到A或B其他的从来都不看,这就尴尬了,C、D、E都给你准备好了,也加载了相应的数据耗费了内存、带宽,结果你却不看。
- add remove replace:初始化Fragment将其add操作,不用了就remove掉。
这样的操作一般用于切换较少的场景,每次remove不仅会移除视图,Fragment的实例也并不会进入FragmentManager的栈中,若没有变量引用会被回收。replace即是remove和add的组合操作。
- attach detach:初始化FragmentA将其add操作,这时切换FragmentB,需要将FragmentA进行detach,然后初始化FragmentB将其add操作,切换回FragmentA这时FragmentManager的栈中存在A的实例,利用tag或id将其find,然后attach操作(add操作栈中将会重复),以此往复。这种操作的优点就是detach时只移除了视图但保存实例到栈中,缺点就是栈中的数据可能永远不会用到。
到这里就结束了放上效果图
由于本人Mac+Chrome+csdn的Markdown 经常崩溃改在简书写作并转载过来,【笑哭】反正也没人看
http://www.jianshu.com/u/0790f0629fc6