Jelajahi Sumber

[feat]键盘插件,实现AI生成内容列表UI

hezihao 8 bulan lalu
induk
melakukan
77b8d57f95

+ 92 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/AiChatComponent.kt

@@ -0,0 +1,92 @@
+package com.atmob.keyboard_android.component
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.atmob.keyboard_android.R
+import com.atmob.keyboard_android.component.base.BaseUIComponent
+import com.atmob.keyboard_android.component.item.AiChatViewBinder
+import com.atmob.keyboard_android.ext.click
+import com.atmob.keyboard_android.model.AiChatModel
+import com.atmob.keyboard_android.util.recyclerview.LinearDivider
+import com.blankj.utilcode.util.ConvertUtils
+import me.drakeet.multitype.Items
+import me.drakeet.multitype.MultiTypeAdapter
+
+/**
+ * Ai对话组件
+ */
+class AiChatComponent @JvmOverloads constructor(
+    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : BaseUIComponent(context, attrs, defStyleAttr) {
+    private lateinit var vBackBtn: View
+    private lateinit var vReGenerateBtn: View
+    private lateinit var vChatList: RecyclerView
+
+    private lateinit var mListItems: Items
+    private lateinit var mListAdapter: MultiTypeAdapter
+
+    override fun onInflateViewId(): Int {
+        return R.layout.component_ai_chat
+    }
+
+    override fun findView(view: View) {
+        vBackBtn = view.findViewById(R.id.back_btn)
+        vReGenerateBtn = view.findViewById(R.id.re_generate_btn)
+        vChatList = view.findViewById(R.id.chat_list)
+    }
+
+    @SuppressLint("NotifyDataSetChanged")
+    override fun bindView(view: View) {
+        vBackBtn.click {
+            // 关闭面板
+        }
+        vReGenerateBtn.click {
+            // 重新生成
+        }
+        vChatList.apply {
+            mListItems = Items()
+            mListAdapter = MultiTypeAdapter(mListItems).apply {
+                register(AiChatModel::class.java, AiChatViewBinder { item ->
+                    // 先全部取消选中
+                    mListItems.filterIsInstance<AiChatModel>().map {
+                        it.isSelected = false
+                    }
+                    // 再选中当前选择的
+                    val targetPosition = mListItems.indexOf(item)
+                    val targetItem = mListItems[targetPosition]
+                    if (targetItem is AiChatModel) {
+                        targetItem.isSelected = true
+                    }
+                    notifyDataSetChanged()
+                })
+            }
+            // 方向
+            val orientation = RecyclerView.VERTICAL
+            layoutManager = LinearLayoutManager(context, orientation, false)
+            adapter = mListAdapter
+            // 分割线
+            addItemDecoration(LinearDivider(ConvertUtils.dp2px(8f), orientation, true))
+        }
+
+        setData()
+    }
+
+    /**
+     * 设置数据
+     */
+    @SuppressLint("NotifyDataSetChanged")
+    private fun setData() {
+        mListItems.apply {
+            add(AiChatModel("巴拉巴拉小魔仙?呵,这种无聊的东西,以后不准再提。", true))
+            add(AiChatModel("巴拉巴拉小魔仙?呵,这种无聊的东西"))
+            add(AiChatModel("巴拉巴拉小魔仙?"))
+            add(AiChatModel("巴拉巴拉小魔仙,好看好看"))
+            add(AiChatModel("巴拉巴拉小魔仙,什么呀,没听过"))
+        }
+        mListAdapter.notifyDataSetChanged()
+    }
+}

+ 48 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/item/AiChatViewBinder.kt

@@ -0,0 +1,48 @@
+package com.atmob.keyboard_android.component.item
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.atmob.keyboard_android.R
+import com.atmob.keyboard_android.ext.click
+import com.atmob.keyboard_android.model.AiChatModel
+import me.drakeet.multitype.ItemViewBinder
+
+/**
+ * AI生成内容的列表项
+ */
+class AiChatViewBinder(
+    private val onItemClick: (item: AiChatModel) -> Unit
+) : ItemViewBinder<AiChatModel, AiChatViewBinder.InnerViewHolder>() {
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup
+    ): InnerViewHolder {
+        return InnerViewHolder(inflater.inflate(R.layout.item_ai_chat, parent, false))
+    }
+
+    override fun onBindViewHolder(
+        holder: InnerViewHolder,
+        item: AiChatModel
+    ) {
+        holder.vContent.apply {
+            text = item.text
+            // 选中
+            if (item.isSelected) {
+                setBackgroundResource(R.drawable.bg_ai_chat_selected)
+            } else {
+                // 未选中
+                setBackgroundResource(R.drawable.bg_ai_chat_normal)
+            }
+        }
+        holder.vContent.click {
+            onItemClick.invoke(item)
+        }
+    }
+
+    inner class InnerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+        val vContent: TextView = itemView.findViewById(R.id.content)
+    }
+}

+ 13 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/model/AiChatModel.kt

@@ -0,0 +1,13 @@
+package com.atmob.keyboard_android.model
+
+import java.io.Serializable
+
+/**
+ * Ai生成内容的模型
+ */
+data class AiChatModel(
+    // 生成的内容
+    val text: String,
+    // 是否选中该条目
+    var isSelected: Boolean = false
+) : Serializable

+ 127 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/util/recyclerview/LinearDivider.kt

@@ -0,0 +1,127 @@
+package com.atmob.keyboard_android.util.recyclerview
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.view.View
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * 线性LinearLayoutManager的分割线
+ */
+class LinearDivider(
+    // 分割线的大小,单位为px
+    private val dividerSizePx: Int,
+    // 方向,垂直还是横向
+    private val orientation: Int = LinearLayoutManager.VERTICAL,
+    // 最后一个条目,是否要有分割线
+    private val lastItemHasDivider: Boolean = true
+) : RecyclerView.ItemDecoration() {
+    /**
+     * 画笔
+     */
+    private val mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+        color = Color.TRANSPARENT
+        style = Paint.Style.FILL
+    }
+
+    override fun getItemOffsets(
+        outRect: Rect,
+        view: View,
+        parent: RecyclerView,
+        state: RecyclerView.State
+    ) {
+        val position = parent.getChildAdapterPosition(view)
+        val totalItemCount = parent.adapter?.itemCount ?: 0
+
+        when (orientation) {
+            LinearLayoutManager.VERTICAL -> {
+                // 垂直列表:下方添加间距(最后一个项不添加)
+                outRect.bottom = if (position < totalItemCount - 1) dividerSizePx else 0
+            }
+
+            LinearLayoutManager.HORIZONTAL -> {
+                // 水平列表:右侧添加间距(最后一个项不添加)
+                outRect.right = if (position < totalItemCount - 1) dividerSizePx else 0
+            }
+        }
+    }
+
+    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
+        when (orientation) {
+            LinearLayoutManager.VERTICAL -> drawVerticalDividers(c, parent)
+            LinearLayoutManager.HORIZONTAL -> drawHorizontalDividers(c, parent)
+        }
+    }
+
+    /**
+     * 纵向分割线
+     */
+    private fun drawVerticalDividers(canvas: Canvas, parent: RecyclerView) {
+        val left = parent.paddingLeft
+        val right = parent.width - parent.paddingRight
+
+        // 绘制方法
+        fun doDraw(childView: View) {
+            val params = childView.layoutParams as RecyclerView.LayoutParams
+            val top = childView.bottom + params.bottomMargin
+            val bottom = top + dividerSizePx
+            canvas.drawRect(
+                left.toFloat(),
+                top.toFloat(),
+                right.toFloat(),
+                bottom.toFloat(),
+                mPaint
+            )
+        }
+
+        // 最后一项,也有分割线
+        val childCount = if (lastItemHasDivider) {
+            parent.childCount
+        } else {
+            // 最后一个项不绘制
+            parent.childCount - 1
+        }
+        for (i in 0 until childCount) {
+            val child = parent.getChildAt(i)
+            doDraw(child)
+        }
+    }
+
+    /**
+     * 横向分割线
+     */
+    private fun drawHorizontalDividers(canvas: Canvas, parent: RecyclerView) {
+        val top = parent.paddingTop
+        val bottom = parent.height - parent.paddingBottom
+
+        // 绘制方法
+        fun doDraw(childView: View) {
+            val params = childView.layoutParams as RecyclerView.LayoutParams
+            val left = childView.right + params.rightMargin
+            val right = left + dividerSizePx
+            canvas.drawRect(
+                left.toFloat(),
+                top.toFloat(),
+                right.toFloat(),
+                bottom.toFloat(),
+                mPaint
+            )
+        }
+
+        // 最后一项,也有分割线
+        val childCount = if (lastItemHasDivider) {
+            parent.childCount
+        } else {
+            // 最后一个项不绘制
+            parent.childCount - 1
+        }
+
+        for (i in 0 until childCount) {
+            val child = parent.getChildAt(i)
+            doDraw(child)
+        }
+    }
+}

+ 11 - 0
plugins/keyboard_android/android/src/main/res/drawable/bg_ai_chat_normal.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <solid android:color="@color/text_color_white" />
+    <corners
+        android:bottomLeftRadius="0dp"
+        android:bottomRightRadius="20dp"
+        android:topLeftRadius="14dp"
+        android:topRightRadius="20dp" />
+</shape>

+ 14 - 0
plugins/keyboard_android/android/src/main/res/drawable/bg_ai_chat_selected.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <solid android:color="@color/text_color_white" />
+    <corners
+        android:bottomLeftRadius="0dp"
+        android:bottomRightRadius="20dp"
+        android:topLeftRadius="14dp"
+        android:topRightRadius="20dp" />
+    <stroke
+        android:width="1dp"
+        android:color="@color/bg_ai_chat_content_border" />
+</shape>

+ 36 - 0
plugins/keyboard_android/android/src/main/res/layout/component_ai_chat.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/back_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@mipmap/ic_back_btn2" />
+
+        <TextView
+            android:id="@+id/re_generate_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="true"
+            android:background="@mipmap/bg_btn_shadow"
+            android:text="@string/re_generate"
+            android:textColor="@color/text_color_primary"
+            android:textSize="12sp" />
+    </RelativeLayout>
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/chat_list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="6dp"
+        android:paddingBottom="15dp" />
+</LinearLayout>

+ 16 - 6
plugins/keyboard_android/android/src/main/res/layout/component_ai_keyboard_content.xml

@@ -38,19 +38,29 @@
         android:layout_height="wrap_content"
         android:orientation="horizontal">
 
-        <androidx.recyclerview.widget.RecyclerView
-            android:id="@+id/key_list"
+        <FrameLayout
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginStart="10dp"
-            android:layout_weight="1"
-            tools:background="@android:color/holo_blue_dark"
-            tools:layout_height="150dp" />
+            android:layout_marginEnd="6dp"
+            android:layout_weight="1">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/key_list"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                tools:layout_height="150dp" />
+
+            <com.atmob.keyboard_android.component.AiChatComponent
+                android:id="@+id/ai_chat_component"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:visibility="gone" />
+        </FrameLayout>
 
         <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginStart="6dp"
             android:layout_marginEnd="10dp"
             android:orientation="vertical">
 

+ 20 - 0
plugins/keyboard_android/android/src/main/res/layout/item_ai_chat.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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="wrap_content">
+
+    <TextView
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/bg_ai_chat_selected"
+        android:lineSpacingMultiplier="1.2"
+        android:paddingStart="11dp"
+        android:paddingTop="8dp"
+        android:paddingEnd="13dp"
+        android:paddingBottom="8dp"
+        android:textColor="@color/text_color_primary"
+        android:textSize="12sp"
+        tools:text="巴拉巴拉小魔仙?呵,这种无聊的东西,以后不准再提。巴拉巴拉小魔仙?呵,这种无聊的东西,以后不准再提。巴拉巴拉小魔仙?呵,这种无聊的东西,以后不准再提。" />
+</FrameLayout>

TEMPAT SAMPAH
plugins/keyboard_android/android/src/main/res/mipmap-xxxhdpi/bg_btn_shadow.9.png


TEMPAT SAMPAH
plugins/keyboard_android/android/src/main/res/mipmap-xxxhdpi/ic_back_btn2.png


+ 2 - 0
plugins/keyboard_android/android/src/main/res/values/colors.xml

@@ -28,4 +28,6 @@
     <color name="bg_tab_indicator_selected">#DDCFFD</color>
     <!-- 粘贴提示文字 -->
     <color name="text_paste_tip">#996DFF</color>
+    <!-- AI生成内容的边框 -->
+    <color name="bg_ai_chat_content_border">#996DFF</color>
 </resources>

+ 1 - 0
plugins/keyboard_android/android/src/main/res/values/string.xml

@@ -17,4 +17,5 @@
     <string name="clear">清空</string>
     <string name="send">发送</string>
     <string name="paste_tip">粘贴你想说的话,润色升华</string>
+    <string name="re_generate">重新生成</string>
 </resources>