Add mavenCentral() (or jcenter()) to repository in your project's build.gradle:
allprojects {
repositories {
...
mavenCentral() // or jcenter()
}
}
Add form to dependencies in your app's build.gradle:
dependencies {
...
implementation 'com.feiyilin:form:0.3.5'
}
- Add FormItem to the adapter
class MainActivity : FormActivity() {
...
override fun initForm() {
adapter?.apply {
+FormItemSection().title("Text").tag("sec_text").apply {
enableCollapse(true)
+FormItemText().title("Text").tag("text").required(true)
+FormItemText().title("Text").subTitle("here is subtitle").tag("text_subtitle")
+FormItemText().title("Text").subTitle("draggable").draggable(true)
.tag("text_draggable")
...
}
}
}
...
}
- override the callbacks
class MainActivity : FormActivity() {
...
override var onFormItemListener: FormItemCallback? = object : FormItemCallback {
override fun onValueChanged(item: FormItem) {
...
}
}
...
}
Or check FormActivity if you want to use FormRecyclerAdapter directly in the activity.
Ver 0.3 supports section. It is a breaking change. Check branch 0.2.x if prefer the old way.
Callback can be used to change the appearance and behavior of an item. It can be set
- in a signle item, for example
+FormItemNav().title("Nav item").tag("nav_item")
.onItemClicked {item, viewHolder ->
Toast.makeText(this@MainActivity, "Click on ${item.title}", Toast.LENGTH_SHORT).show()
}
- Or in FormItemCallback (for multiple items)
Supported callbacks
-
onSetup
Called when the item is configured.
-
onValueChanged
Called when the value of an item changes.
-
onItemClicked
Called when an item is clicked.
-
onTitleImageClicked
Called when the title icon is clicked
-
onStartReorder
Called before moving/reordering an item. Return true from the callback to disable the default action.
-
onMoveItem
Called before finishing moving an item. Return true from the callback to disable the default action.
-
onSwipedAction
Called when a swipe action is triggered.
-
onEditorAction
"called when the enter key is pressed , or when an action supplied to the IME is selected by the user" (text item)
-
getMinItemHeight (FormItemCallback only)
Called when configure/bind an item. Can be used to update the minimum height for all (or a group of) items.
Use + operator to add an item or a section to adapter
adapter?.apply {
// add a section
+FormItemSection().apply {
// add item to section
+FormItemNav().title("Item 0")
}
}
Or call add
val sec = FormItemSection().title("New section").apply {
+FormItemNav().title("Item 0")
+FormItemNav().title("item 1")
}
sec.add(FormItemNav().title("Item 2"))
adapter?.add(sec)
And to remove an item or a section from adatper
// remove a section
adapter?.remove(sec)
// remove an item
adapter?.remove(item)
// or
sec.remove(item)
Tag can be used to access an item or section
val item = adapter.itemBy("item_tag") // or null if not found
val section = adapter.sectionBy("section_tag")
To collapse/expand a section (show/hide its children),
- enable collapse/expand on the section, which will also show an indicator icon
section.enableCollapse(true)
- collapse/expand the section by calling "adapter.collapse"
override fun onItemClicked(item: FormItem, viewHolder: RecyclerView.ViewHolder) {
if (item is FormItemSection) {
if (item.enableCollapse) {
adapter?.collapse(item, !item.collapsed)
}
}
...
}
Call adapter.hide to dynamically show/hide item/section. If it is a section, it will hide the section item and all its visible children.
For example
// hide an item
adapter.itemBy("action")?.let {
adapter.hide(it, true)
}
// hide a section
adapter.sectionBy("sec_date")?.let {
adapter.hide(it, true)
}
FormItemRadios will be considered to be in the same group (i.e., selecting one will de-select others), if
- they have the same group name, and
- in the same section.
+FormItemSection().title("Radio").apply {
+FormItemRadio().group("radio0").title("item 0")
.tag("radio0_item0").isOn(true)
+FormItemRadio().group("radio0").title("item 1")
.tag("radio0_item1")
...
}
For each item, we can define the leading/left or trailing/right swipe actions (following the idea here). For example
FormItemNav().title("Swipe left with multiple actions").trailingSwipe(listOf(
FormSwipeAction().title("Delete")
.backgroundColor(ContextCompat.getColor(this, android.R.color.holo_red_light)),
FormSwipeAction().title("Archive")
.backgroundColor(ContextCompat.getColor(this, android.R.color.holo_blue_light)),
FormSwipeAction().title("Mark as unread")
.backgroundColor(ContextCompat.getColor(this, android.R.color.holo_green_light))
)),
Once an action is triggered, onSwipedAction callback will be called
override fun onSwipedAction(
item: FormItem,
action: FormSwipeAction,
viewHolder: RecyclerView.ViewHolder
) {
super.onSwipedAction(item, action, viewHolder)
Toast.makeText(this@MainActivity, "${item.title}: ${action.title}", Toast.LENGTH_SHORT).show()
}
Text | |
Text area | |
Label | |
Number | |
Switch | |
Radio | |
SeekBar | |
Stepper | |
Nav | |
Action | |
Date | |
Select | |
Picker inline | |
Color |
- Design the layout of your item, e.g., form_item_image.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="@+id/profile_image_wrap"
app:cardCornerRadius="63dp"
android:layout_width="126dp"
android:layout_height="126dp"
android:layout_marginTop="9dp"
android:layout_marginBottom="9dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_gravity="center_horizontal"
app:cardBackgroundColor="#00FFFFFF">
<ImageView
android:id="@+id/formELementImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
app:srcCompat="@drawable/form_image_placeholder"
android:scaleType="fitCenter"/>
</androidx.cardview.widget.CardView>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/profile_image_wrap"
android:background="#FFE0E0E0"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- Derive an item from FormItem,
open class FormItemImage : FormItem() {
var image: Int = 0
}
fun <T : FormItemImage> T.image(image: Int) = apply {
this.image = image
}
- Derive a view holder class from FormViewHolder, and override bind
class FormImageViewHolder(inflater: LayoutInflater, resource: Int, parent: ViewGroup) :
FormViewHolder(inflater, resource, parent) {
private var imgView: ImageView? = null
init {
imgView = itemView.findViewById(R.id.formELementImage)
}
override fun bind(s: FormItem, listener: FormItemCallback?) {
if (s is FormItemImage) {
Picasso.get().load(s.image).fit().centerInside().into(imgView)
imgView?.setOnClickListener {
listener?.onValueChanged(s)
}
}
}
}
- Register the item with registerViewHolder
class MainActivity : FormActivity() {
...
override fun initForm() {
...
adapter?.registerViewHolder(
FormItemImage::class.java,
R.layout.form_item_image,
FormImageViewHolder::class.java
)
...
}
}