仿支付宝更多功能的实现

    xiaoxiao2022-07-03  114

    类似于功能中心这样的设计经常看到,公司这块的项目刚好有需求,自己仿着支付宝做了一下,实现的思路比较多,这里说下我的思路

    先看下效果:

    主要实现功能如下:

    1. tab与recyclerview支持滑动联动

    2. 我的应用支持拖拽排序

    3. 支持动态添加删除

    总体上与支付宝是差不多的

    思路如下:

    布局分析:

     

    布局层级如上图:

    xml如下:

    <?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay" android:background="@color/white" > <!--app:layout_scrollFlags="scroll|exitUntilCollapsed"--> <LinearLayout android:id="@+id/layout_app_section" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="@color/white" > <RelativeLayout android:layout_width="match_parent" android:layout_height="48dp" android:paddingLeft="@dimen/dp_16" android:paddingStart="@dimen/dp_16" android:paddingRight="@dimen/dp_16" android:paddingEnd="@dimen/dp_16" > <TextView android:id="@+id/tv_header_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="我的应用" android:background="@color/white" android:textSize="@dimen/sp_18" android:textStyle="bold" android:layout_centerVertical="true" android:textColor="@color/title_black" > </TextView> <TextView android:id="@+id/tv_edit_des" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="长按编辑,拖拽进行排序" android:textColor="@color/textColorPrimary" android:textSize="@dimen/sp_14" android:layout_marginLeft="@dimen/dp_16" android:layout_toRightOf="@id/tv_header_name" android:layout_alignBottom="@id/tv_header_name" android:visibility="visible" /> <TextView android:id="@+id/tv_edit" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="编辑" android:textColor="@color/colorPrimary" android:textSize="@dimen/sp_18" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_centerVertical="true" /> </RelativeLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_app_section" android:layout_width="match_parent" android:layout_height="wrap_content" android:nestedScrollingEnabled="false" android:overScrollMode="never" /> </LinearLayout> <com.google.android.material.tabs.TabLayout android:id="@+id/tabLayout" android:layout_height="@dimen/dp_40" android:layout_width="match_parent" app:tabTextColor="@color/common_color" app:tabSelectedTextColor="@color/colorPrimary" app:tabIndicatorColor="@color/colorPrimary" app:tabBackground="@color/white" app:tabMode="scrollable" app:tabTextAppearance="@style/TabLayoutTextStyle" /> </com.google.android.material.appbar.AppBarLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" android:scrollbars="vertical" /> </androidx.coordinatorlayout.widget.CoordinatorLayout>

    这里采用2个recyclerview,1个tablayout,根部局采用CoordinatorLayout,如果有tablayout悬停需求,可以指定 app:layout_scrollFlags="scroll|exitUntilCollapsed",xml中已做说明。

    另外底部的RecyclerView带有标题,又可以采用两种方式:

    1.RecyclerView嵌套RecyclerView

    优点是相对比较容易与tablayout做联动,但是滑动有卡顿

    2. RecyclerView根据item 的不同,加载不同的item,采用一个RecyclerView

    优点是滑动比较流畅,但是RecyclerView header的position需要和tablayout的标题相关联,麻烦些

    appItem布局比较简单,就不再贴了

    只要UI控件确定了,其他就按此写逻辑就可以了。

    这里主要演示一下第二种方式,体验上好点:

    Adapter采用的BaseRecyclerViewAdapterHelper  包括拖拽监听等,这个比较简单,照着官方demo就行,现在主要是tablayout与RecyclerView怎么联动起来

    主界面代码:

    /** * section * 不采用RecyclerView嵌套RecyclerView方式 * 单一RecyclerView */ class RecyclerFragment3 : Fragment() { /** * tab 的position和RecyclerView的Header的position的映射 */ private var posMap: MutableMap<Int, Int> = HashMap() private val divider by lazy { DividerItemDecoration(activity, DividerItemDecoration.VERTICAL) } private val mAdapter by lazy { SectionAdapter2(DataUtil.getSectionData2()) } private val mmanager by lazy { GridLayoutManager(activity, 4) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { // 参数:布局id,指定父布局协助生成布局参数,是否加载进父布局 return inflater.inflate(R.layout.fragment_section, null) } companion object { fun getInstance() = RecyclerFragment3() } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) DataUtil.getTabNames().forEachIndexed { index, it -> tab.addTab(tab.newTab().setText(it.header).setTag(it.subItemPos)) posMap.put(it.subItemPos,index) } tab.addOnTabSelectedListener(tabSelectedListener) recycler.run { layoutManager = mmanager adapter = mAdapter mAdapter.onItemClickListener = listener addItemDecoration(divider) addOnScrollListener(scrollListener) // 添加footer,以便使tab能滑动到底 post { val data = DataUtil.getSectionData() val lastAppSectionRowCount = Math.ceil(data[data.count() - 1].data.size / 4.0).toInt() val headerHeight = getChildAt(0).height val itemHeight = getChildAt(1).height val footviewHeight = height - (headerHeight + itemHeight * lastAppSectionRowCount) val footView = View(activity) footView.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, footviewHeight) mAdapter.addFooterView(footView) } } } private val listener = object : BaseQuickAdapter.OnItemClickListener { override fun onItemClick(adapter: BaseQuickAdapter<*, *>?, view: View?, position: Int) { Toast.makeText(activity, "pos:${position}", Toast.LENGTH_SHORT).show() } } private val scrollListener = object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { val pos = posMap[mmanager.findFirstVisibleItemPosition()] if (pos != null) { tab.setScrollPosition(pos, 0f, true) } } } private val tabSelectedListener = object : TabLayout.OnTabSelectedListener { override fun onTabReselected(p0: TabLayout.Tab) { } override fun onTabUnselected(p0: TabLayout.Tab) { } override fun onTabSelected(tab: TabLayout.Tab) { mmanager.scrollToPositionWithOffset(tab.tag as Int, 0) } } }

    SectionAdapter代码:

    class SectionAdapter2(data: List<MySection>) : BaseSectionQuickAdapter<MySection, BaseViewHolder>(R.layout.item_section_item,R.layout.item_section_header, data) { override fun convertHead(helper: BaseViewHolder?, item: MySection?) { helper ?: return item ?: return helper.run { setText(R.id.tv_item_name, item.header) } } override fun convert(helper: BaseViewHolder, item: MySection) { val bean = item.t helper.run { setText(R.id.tv_item_name, bean.name) setImageResource(R.id.icon,R.mipmap.ic_launcher_round) } } }

    这里直接继承的官方的Adapter,其实就是根据不同布局类型加载对应数据的adapter

    DataUtil代码:

    object DataUtil { private var sData: ArrayList<String>? = null private var sectionData: ArrayList<SectionBean>? = null private var tabNames: ArrayList<MySection>? = null private var sectionData2: ArrayList<MySection>? = null fun getStringData(): ArrayList<String> { if (sData == null) { sData = ArrayList() for (i in 0..20) { sData!!.add("第 $i 个条目") } } return sData!! } /** * 二维数据结构 */ fun getSectionData(): ArrayList<SectionBean> { if (sectionData == null) { sectionData = ArrayList() for (i in 0..8) { val items = ArrayList<ItemBean>() for (j in 0..7) { items.add(ItemBean(R.mipmap.ic_launcher_round, "item$j")) } sectionData!!.add(SectionBean("区块$i", items)) } } return sectionData!! } /** * 一维数据结构 */ fun getSectionData2():ArrayList<MySection>{ if (sectionData2 == null) { sectionData2 = ArrayList() val sectionData = getSectionData() var subItemPos = 0 sectionData.forEach { sectionData2!!.add(MySection(header = it.title, isHeader = true, subItemPos = subItemPos)) it.data.forEach { sectionData2!!.add(MySection(it)) } subItemPos += it.data.size + 1 // 转化成一维列表时的position } } return sectionData2!! } fun getTabNames(): ArrayList<MySection> { if (tabNames == null) { tabNames = ArrayList() val sectionData = getSectionData() var subItemPos = 0 sectionData.forEach { tabNames!!.add(MySection(header = it.title, isHeader = true, subItemPos = subItemPos)) subItemPos += it.data.size + 1 // 转化成一维列表时的position } } return tabNames!! } }

    DataBean相关代码:

    data class SectionBean( val title: String, val data: List<ItemBean> ) data class ItemBean( @DrawableRes val icon_url: Int = R.mipmap.ic_launcher_round, val name: String ) /** * section Header */ class MySection : SectionEntity<ItemBean> { var subItemPos = 0 constructor(isHeader: Boolean = false, header: String, subItemPos: Int) : super(isHeader, header) { this.subItemPos = subItemPos } constructor(t: ItemBean) : super(t) }

    这里需要注意的是使用BaseSectionQuickAdapter时对数据结构有一定要求。

    MySection中的subItemPos字段就是header转换为一维数组后的新的position。

    这里的两个RecyclerView的添加删除我也没有写,都是对数据操作的事情,细心点问题应该都能解决,这里有几点需要注意:

    1. 处于编辑模式后,item图标的刷新,就是显示删除添加图标,可以使用notifyItemChanged(),但是刷新会有一些闪动,个人觉得直接循环更改图标的样式会好些。

    2. 由于RecyclerView有复用,要多注意RecyclerView item根据不同状态的显示变化。

    3. 为了使tablayout能滑动到最后,为RecyclerView添加了footview

    4. 注意一维数据和二维数据的转换

    以上

     

     

     

    最新回复(0)