How to make a recycler view with different item elevation onScroll
[
Like this video
if you're not already using ViewPager2 you should try to check what it does, you can apply transformations to pages (items). As you haven't provided any code, i can only reply in a general context.
You can achieve the result you want with the following code:
ViewPager2.PageTransformer { page, position ->
page.apply {
translationY = Math.abs(position) * 500f
scaleX = 1f
scaleY = 1f
}
}
then
viewPager2.setPageTransformer(CompositePageTransformer().also {
it.addTransformer(marginPageTransformer)
it.addTransformer(translationPageTransformer())
})
check this link for more explanation
Related
I have a Column with remember scroll state. I am using a derived state as follows.
val isHidden = remember {
derivedStateOf {
columnState.value > 400
}
}
I want to display a search bar whenever it scrolls beyond 400 pixels.
Another approach i used is to create a state based on offset of first item in Column using function onGloballyPositioned.
The modifier of first item is as follows:
Box(modifier = Modifier
.graphicsLayer { translationX = horizontalOffset.value }
.onGloballyPositioned { coordinates ->
isHidden.value = coordinates.boundsInRoot().top == 0f
}) {
The problem is no matter which approach i choose it always lags when the state is changed (when the value checks for < or > 400) or in case of onGloballyPositioned if the Compose reaches top of the screen i.e. y coordinate of view == 0.
In both of the approaches isHidden is going to be true. Changing is hidden in both cases causing lag at the point of change.
I can't use lazy row because of some restrictions. Is there any solution to implement this without the lag.
In my XML I'm just declaring a ChipGroup as follows:
<com.google.android.material.chip.ChipGroup
android:id="#+id/chipGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
And then adding each Chip dynamically (where CustomChipFilterStyle styles them as a "filter" type of Chip):
ChipGroup chipGroup = findViewById(R.id.chipGroup);
for (String name : names) {
Chip chip = new Chip(this, null, R.attr.CustomChipFilterStyle);
chip.setText(name);
chipGroup.addView(chip);
}
In the guidance (see the video clip under "Movable") it suggests that "Input chips can be reordered or moved into other fields":
But I can't see any guidance about how this is done, or find any examples out there. Is it a completely bespoke thing (via View.OnDragListener and chip.setOnDragListener()), or are there utility methods for this as part of the Chip framework? All I really need to be able to do is to reorder Chips within the same ChipGroup. I did start with chip.setOnDragListener() but soon realised I didn't have sufficient knowledge about how to create the necessary animations to nudge and re-order other Chips as the Chip itself is being dragged (and to distinguish between a tap -- to filter -- and a drag)... and I hoped that there might be some out-of-the-box way of doing this with a ChipGroup like is maybe alluded to in the above guidance.
But I can't see any guidance about how [chip reordering within a ChipGroup] is done, or find any examples out there.
It is surprising that there doesn't seem to be an "out-of-the-box" way to reorder chips in a ChipGroup - at least not one that I have found.
All I really need to be able to do is to reorder Chips within the same ChipGroup.
I did start with chip.setOnDragListener() but soon realised I didn't have sufficient knowledge about how to create the necessary animations to nudge and re-order other Chips as the Chip itself is being dragged
The following doesn't really fully answer your question since the answer involves a RecyclerView and not a ChipGroup, but the effect is the same. This solution is based up the ItemTouchHelper Demo
by Paul Burke. I have converted the Java to Kotlin and made some modifications to the code. I have posted a demo repo at ChipReorder The layout manager I use for the RecyclerView is FlexboxLayoutManager.
The demo app relies upon ItemTouchHelper which is a utility class that adds swipe to dismiss and drag & drop support to RecyclerView. If you look at the actual code of ItemTouchHelper, you will get an idea of the underlying complexity of the animation that appears on the screen for a simple drag.
Here is a quick video of chips being dragged around using the demo app.
I believe that any functionality that you may need from ChipGroup can be implemented through RecyclerView or its adapter.
Update: I have added a module to the demo repo called "chipgroupreorder" which reorders chips within a ChipGroup with animation. Although this looks much the same as the RecyclerView solution, it uses a ChipGroup and not a RecyclerView.
The demo uses a View.OnDragListener and relies upon android:animateLayoutChanges="true" that is set for the ChipGroup for the animations.
The selection of which view to shift is rudimentary and can be improved. There are probably other issues that may arise upon further testing.
As you suggested there's no out-of-the-box solution for this. So I've made a sample project to show usage of setOnDragListener & how you can create something like this for yourself.
Note: This is far from being the perfect polished solution that you might expect but I believe it can nudge you in the right direction.
Complete code: https://github.com/mayurgajra/ChipsDragAndDrop
Output:
Pasting code here as well with inline comments:
MainActivity
class MainActivity : AppCompatActivity() {
private val dragMessage = "Chip Added"
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val names = mutableListOf("Name 1", "Name 2", "Name 3")
for (name in names) {
val chip = Chip(this, null, 0)
chip.text = name
binding.chipGroup1.addView(chip)
}
attachChipDragListener()
binding.chipGroup1.setOnDragListener(chipDragListener)
}
private val chipDragListener = View.OnDragListener { view, dragEvent ->
val draggableItem = dragEvent.localState as Chip
when (dragEvent.action) {
DragEvent.ACTION_DRAG_STARTED -> {
true
}
DragEvent.ACTION_DRAG_ENTERED -> {
true
}
DragEvent.ACTION_DRAG_LOCATION -> {
true
}
DragEvent.ACTION_DRAG_EXITED -> {
//when view exits drop-area without dropping set view visibility to VISIBLE
draggableItem.visibility = View.VISIBLE
view.invalidate()
true
}
DragEvent.ACTION_DROP -> {
//on drop event in the target drop area, read the data and
// re-position the view in it's new location
if (dragEvent.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
val draggedData = dragEvent.clipData.getItemAt(0).text
println("draggedData $draggedData")
}
//on drop event remove the view from parent viewGroup
if (draggableItem.parent != null) {
val parent = draggableItem.parent as ChipGroup
parent.removeView(draggableItem)
}
// get the position to insert at
var pos = -1
for (i in 0 until binding.chipGroup1.childCount) {
val chip = binding.chipGroup1[i] as Chip
val start = chip.x
val end = (chip.x + (chip.width / 2))
if (dragEvent.x in start..end) {
pos = i
break
}
}
//add the view view to a new viewGroup where the view was dropped
if (pos >= 0) {
val dropArea = view as ChipGroup
dropArea.addView(draggableItem, pos)
} else {
val dropArea = view as ChipGroup
dropArea.addView(draggableItem)
}
true
}
DragEvent.ACTION_DRAG_ENDED -> {
draggableItem.visibility = View.VISIBLE
view.invalidate()
true
}
else -> {
false
}
}
}
private fun attachChipDragListener() {
for (i in 0 until binding.chipGroup1.childCount) {
val chip = binding.chipGroup1[i]
if (chip !is Chip)
continue
chip.setOnLongClickListener { view: View ->
// Create a new ClipData.Item with custom text data
val item = ClipData.Item(dragMessage)
// Create a new ClipData using a predefined label, the plain text MIME type, and
// the already-created item. This will create a new ClipDescription object within the
// ClipData, and set its MIME type entry to "text/plain"
val dataToDrag = ClipData(
dragMessage,
arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
item
)
// Instantiates the drag shadow builder.
val chipShadow = ChipDragShadowBuilder(view)
// Starts the drag
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
//support pre-Nougat versions
#Suppress("DEPRECATION")
view.startDrag(dataToDrag, chipShadow, view, 0)
} else {
//supports Nougat and beyond
view.startDragAndDrop(dataToDrag, chipShadow, view, 0)
}
view.visibility = View.INVISIBLE
true
}
}
}
}
ChipDragShadowBuilder:
class ChipDragShadowBuilder(view: View) : View.DragShadowBuilder(view) {
//set shadow to be the drawable
private val shadow = ResourcesCompat.getDrawable(
view.context.resources,
R.drawable.shadow_bg,
view.context.theme
)
// Defines a callback that sends the drag shadow dimensions and touch point back to the
// system.
override fun onProvideShadowMetrics(size: Point, touch: Point) {
// Sets the width of the shadow to full width of the original View
val width: Int = view.width
// Sets the height of the shadow to full height of the original View
val height: Int = view.height
// The drag shadow is a Drawable. This sets its dimensions to be the same as the
// Canvas that the system will provide. As a result, the drag shadow will fill the
// Canvas.
shadow?.setBounds(0, 0, width, height)
// Sets the size parameter's width and height values. These get back to the system
// through the size parameter.
size.set(width, height)
// Sets the touch point's position to be in the middle of the drag shadow
touch.set(width / 2, height / 2)
}
// Defines a callback that draws the drag shadow in a Canvas that the system constructs
// from the dimensions passed in onProvideShadowMetrics().
override fun onDrawShadow(canvas: Canvas) {
// Draws the Drawable in the Canvas passed in from the system.
shadow?.draw(canvas)
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent"
android:background="#color/white"
android:orientation="vertical"
tools:context=".MainActivity">
<com.google.android.material.chip.ChipGroup
android:id="#+id/chipGroup1"
android:layout_width="match_parent"
android:layout_height="56dp"
app:singleSelection="true">
</com.google.android.material.chip.ChipGroup>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#555" />
</LinearLayout>
For understanding how drag works in detail. I would suggest you read: https://www.raywenderlich.com/24508555-android-drag-and-drop-tutorial-moving-views-and-data
I'm having troubles with some animation in a recycler view. I do the relevant measurements in onViewAttachedToWindow:
override fun onViewAttachedToWindow(holder: PairingViewHolder) {
super.onViewAttachedToWindow(holder)
// get originalHeight & expandedHeight if not gotten before
if (holder.expandedHeight < 0) {
// Execute pending bindings, otherwise the measurement will be wrong.
holder.itemViewDataBinding.executePendingBindings()
holder.cardContainer.layoutParams.width = expandedWidth
holder.expandedHeight = 0 // so that this block is only called once
holder.cardContainer.doOnLayout { view ->
holder.originalHeight = view.height
holder.expandView.isVisible = true
// show expandView and record expandedHeight in next layout pass
// (doOnPreDraw) and hide it immediately.
view.doOnPreDraw {
holder.expandedHeight = view.height
holder.expandView.isVisible = false
holder.cardContainer.layoutParams.width = originalWidth
}
}
}
}
The problem is that doOnPreDraw gets called just for some views. It is something related to the visibility of the views I guess, since the smaller the items (expanded) are, the highest the count of the ones on which onPreDraw gets called.
My guess is that since I'm expanding them in onLayout, the recyclerView consider visible only the ones that when expanded are actually visible on screen. In onPreDraw I collapse them, resulting in some views being able to animate correctly and some not.
How would you solve this?
Thanks in advance.
HI i have the below animation for a view below:
val duration = 2000L
val visible = 1.0f
imageAVater.apply {
animate().translationYBy(-100f).alpha(visible).setDuration(duration).setListener(object : AnimatorListenerAdapter(){
override fun onAnimationEnd(animation: Animator?) {
visibility = View.VISIBLE
}
})
}
i want it to move from slightly off-position and into position and to also reveal itself by setting the alpha .
SO far neither works.
ALl the code above does is move the image from current default position on my layout(lets say i positioned it in xml along the Y axis position 200) and then it moves from position 200 to position 100 and also the alpha does not work, the item is visible all the time despite it being set to View.Gone in my xml
android:visibility="gone"
How can i set a start and end Y axis value for this translation animation and how can i get the alpha to work so that the view appears from hidden/gone?
i want it to start at 200 y and have it transition to 100 y and to also reveal itself from being hidden/gone to being shown at the same time as the transition
you need to specify starting values for y and alpha. EG.
imageAVater.apply {
alpha = 0f
animate().alpha(1f).setDuration(2000).start()
}
leave the view's visibility always to visible. You can not animate a view that is gone
I am trying to implement a vertical swipeable ViewPager with stack of cards like appearance.
I am able to achieve VerticalViewPager using ViewPager.PageTransformer and swapping the touch points.
I am getting following card appearance -
I want to achieve following appearance -
How can I achieve this effect?
Thanks in Advance.
In order to achieve this vertical View Pager without any library dependency
You can follow the steps below :
In your activity_main.xml
<android.support.v4.view.ViewPager
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="600dp"
android:id="#+id/viewpager">
</android.support.v4.view.ViewPager>
In your viewpager_contents.xml
You can create the design that you want according to the above
picture.
Create the adapter and model class to hold the data.
4.In MainActivity.java, after setting the adapter and the pageTransformer add the following code.
private class ViewPagerStack implements ViewPager.PageTransformer {
#Override
public void transformPage(View page, float position) {
if (position >= 0) {
page.setScaleX(0.7f - 0.05f * position);
page.setScaleY(0.7f);
page.setTranslationX(-page.getWidth() * position);
page.setTranslationY(30 * position);
}
}
}
For detailed reference you can watch this video:
CLICK HERE
Hope you will get the output what you needed!!
none of the above solutions worked for me peroperly.
this is my solution :
My PageTransformer class :
class ViewPagerCardTransformer : ViewPager.PageTransformer {
override fun transformPage(page: View, position: Float) {
if (position >= 0) {
page.scaleX = 0.9f - 0.05f * position
page.scaleY = 0.9f
page.alpha = 1f - 0.3f * position
page.translationX = -page.width * position
page.translationY = -30 * position
} else {
page.alpha = 1 + 0.3f * position
page.scaleX = 0.9f + 0.05f * position
page.scaleY = 0.9f
page.translationX = page.width * position
page.translationY = 30 * position
}
}
}
code in my fragment :
val adapter = MyPagerAdapter(pageItems)
viewPager.adapter = adapter
/*below line will increase the number of cards stacked on top of
each other it is set to 1 by default. don't increase it too much as you
will end up with too much view page items in your memory
*/
mDaysViewPager.offscreenPageLimit = 3
viewPager.setPageTransformer(true, ViewPagerCardTransformer())
I'm Using this code for vertical card view stack
https://github.com/AndroidDeveloper98/StackViewPager
You could use the SwipeStack library. It does exactly what you want - it is basically a viewgroup that inflates views based on your adapter implementation, placing them one on top of the other.
The magic is here https://github.com/yuyakaido/CardStackView. You can easily custom what you want. Happy coding :)