Android RecyclerView height problem inside view pager2 inside NestedScrollView with TabLayout [duplicate] - java

There are a few posts on getting ViewPager to work with varying height items that center around extending ViewPager itself to modify its onMeasure to support this.
However, given that ViewPager2 is marked as a final class, extending it isn't something that we can do.
Does anyone know if there's a way to make this work out?
E.g. let's say I have two views:
View1 = 200dp
View2 = 300dp
When the ViewPager2 (layout_height="wrap_content") loads -- looking at View1, its height will be 200dp.
But when I scroll over to View2, the height is still 200dp; the last 100dp of View2 is cut off.

The solution is to register a PageChangeCallback and adjust the LayoutParams of the ViewPager2 after asking the child to re-measure itself.
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
val view = // ... get the view
view.post {
val wMeasureSpec = MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY)
val hMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
view.measure(wMeasureSpec, hMeasureSpec)
if (pager.layoutParams.height != view.measuredHeight) {
// ParentViewGroup is, for example, LinearLayout
// ... or whatever the parent of the ViewPager2 is
pager.layoutParams = (pager.layoutParams as ParentViewGroup.LayoutParams)
.also { lp -> lp.height = view.measuredHeight }
}
}
}
})
Alternatively, if your view's height can change at some point due to e.g. asynchronous data load, then use a global layout listener instead:
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
private val listener = ViewTreeObserver.OnGlobalLayoutListener {
val view = // ... get the view
updatePagerHeightForChild(view)
}
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
val view = // ... get the view
// ... IMPORTANT: remove the global layout listener from other views
otherViews.forEach { it.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener) }
view.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
}
private fun updatePagerHeightForChild(view: View) {
view.post {
val wMeasureSpec = MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY)
val hMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
view.measure(wMeasureSpec, hMeasureSpec)
if (pager.layoutParams.height != view.measuredHeight) {
// ParentViewGroup is, for example, LinearLayout
// ... or whatever the parent of the ViewPager2 is
pager.layoutParams = (pager.layoutParams as ParentViewGroup.LayoutParams)
.also { lp -> lp.height = view.measuredHeight }
}
}
}
}
See discussion here:
https://issuetracker.google.com/u/0/issues/143095219

In my case, adding adapter.notifyDataSetChanged() in onPageSelected helped.

Just do this for the desired Fragment in ViewPager2:
override fun onResume() {
super.onResume()
layoutTaskMenu.requestLayout()
}
Jetpack: binding.root.requestLayout() (thanks #syed-zeeshan for the specifics)

Stumbled across this case myself however with fragments.
Instead of resizing the view as the accepted answer I decided to wrap the view in a ConstraintLayout. This requires you to specify a size of your ViewPager2 and not use wrap_content.
So Instead of changing size of our viewpager it will have to be minimum size of the largest view it handles.
A bit new to Android so don't know if this is a good solution or not, but it does the job for me.
In other words:
<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="match_parent"
>
<!-- Adding transparency above your view due to wrap_content -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
>
<!-- Your view here -->
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

For me this worked perfectly:
viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
super.onPageScrolled(position,positionOffset,positionOffsetPixels)
if (position>0 && positionOffset==0.0f && positionOffsetPixels==0){
viewPager2.layoutParams.height =
viewPager2.getChildAt(0).height
}
}
})

Just call .requestLayout() to the root view of layout in the onResume() of your Fragment class which is being used in ViewPager2

Just Add this small code in your all fragments of ViewPager2
#Override
public void onResume() {
super.onResume();
binding.getRoot().requestLayout();
}
This is working for me perfectly (If you are not using binding then Just get a root layout instance in place of binding)

I had a similar problem and solved it as below.
In my case I had ViewPager2 working with TabLayout with fragments with different heights.
In each fragment in the onResume() method, I added the following code:
#Override
public void onResume() {
super.onResume();
setProperHeightOfView();
}
private void setProperHeightOfView() {
View layoutView = getView().findViewById( R.id.layout );
if (layoutView!=null) {
ViewGroup.LayoutParams layoutParams = layoutView.getLayoutParams();
if (layoutParams!=null) {
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutView.requestLayout();
}
}
}
R.id.layout is layout of particular fragment.
I hope I helped.
Best regards,
T.

No posted answer was entirely applicable for my case - not knowing the height of each page in advance - so I solved different ViewPager2 pages heights using ConstraintLayout in the following way:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="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"
>
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
>
<!-- ... -->
</com.google.android.material.appbar.AppBarLayout>
<!-- Wrapping view pager into constraint layout to make it use maximum height for each page. -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/viewPagerContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#id/bottomNavigationView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/appBarLayout"
>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/bottom_navigation_menu"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

#Mephoros code works perfectly when swiped between views but won't work when views are peeked for first time. It works as intended after swiping it.
So, swipe viewpager programmatically:
binding.viewpager.setCurrentItem(1)
binding.viewpager.setCurrentItem(0) //back to initial page

I'm using the ViewPager2ViewHeightAnimator from here

I got stuck with this problem too. I was implementing TabLayout and ViewPager2 for several tabs with account information. Those tabs had to be with different heights, for example: View1 - 300dp, View2 - 200dp, Tab3 - 500dp. The height was locked within first view's height and the others were cut or extended to (example) 300dp. Like so:
So after two days of searches nothing helped me (or i had to try better) but i gave up and used NestedScrollView for all my views. For sure, now i don't have effect, that the header of profile scrolls with info in 3 views, but at least it now works somehow.
Hope this one helps someone! If you have some advices, feel free to reply!
P.s. I'm sorry for my bad english skills.

Only adapter.notifyDataSetChanged() worked for me in ViewPager2. Used below code in Kotlin.
viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
adapter.notifyDataSetChanged()
}
})

why don't you do it by replacing not using ViewPager2.
like code in below:
private void fragmentController(Fragment newFragment){
FragmentTransaction ft;
ft = mainAct.getSupportFragmentManager().beginTransaction();
ft.replace(R.id.relMaster, newFragment);
ft.addToBackStack(null);
ft.commitAllowingStateLoss();
}
Where relMaster is RelativeLayout.

Answer by #Mephoros worked for me in the end. I had a Recyclerview with pagination(v3) in one of the fragments and it was behaving really strangely with page loads. Here is a working snippet based on the answer in case anyone has problems getting and cleaning views.
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
var view : View? = null
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
view?.let {
updatePagerHeightForChild(it)
}
}
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
// ... IMPORTANT: remove the global layout listener from other view
view?.viewTreeObserver?.removeOnGlobalLayoutListener(layoutListener)
view = (viewPager[0] as RecyclerView).layoutManager?.findViewByPosition(position)
view?.viewTreeObserver?.addOnGlobalLayoutListener(layoutListener)
}
private fun updatePagerHeightForChild(view: View) {
view.post {
val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
view.measure(wMeasureSpec, hMeasureSpec)
if (viewPager.layoutParams.height != view.measuredHeight) {
viewPager.layoutParams = (viewPager.layoutParams)
.also { lp -> lp.height = view.measuredHeight }
}
}
}
})

just add this code to each fragments :
override fun onResume() {
super.onResume()
binding.root.requestLayout()
}

Finally, I can fix this without requestLayout, notifyDataChanged, or the other solutions above!
It's really easy and simple!
You just need to save current height onPause, then load the saved height onResume.
Look at this example code:
public class MyTabbedFragment extends Fragment {
public MyTabbedFragmentViewBinding binding;
String TAG = "MyTabbedFragment";
int heightBeforePause;
// other code
#Override
public void onResume() {
super.onResume();
Log.d(TAG, "lifecycle | onResume | before set height | rec view height: " + binding.recycleView.getHeight() + " | height before pause: " + heightBeforePause);
// load the saved height
if(heightBeforePause > 0) {
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, heightBeforePause);
binding.recycleView.setLayoutParams(layoutParams);
}
}
#Override
public void onPause() {
super.onPause();
// save the current height
heightBeforePause = binding.recycleView.getHeight();
Log.d(TAG, "lifecycle | onPause | rec view height: " + binding.recycleView.getHeight());
}

viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
val view = (viewPager[0] as RecyclerView).layoutManager?.findViewByPosition(position)
view?.post {
val wMeasureSpec = MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY)
val hMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
view.measure(wMeasureSpec, hMeasureSpec)
if (viewPager.layoutParams.height != view.measuredHeight) {
viewPager.layoutParams = (viewPager.layoutParams).also { lp -> lp.height = view.measuredHeight }
}
}
}
})

Related

Drag and drop ImageView into a container for verification

To understand it better read this :
First game :
Skate QuestionMark
Archery QuestionMark
Swim QuestionMark
---------------------------
Water Bow Wheel
If user drags Water to Skate or Archery QuestionMark it will animate to the list (because it is not correct)
If user drags twice incorrect (it will mark the one that is correct from the answer list)
If user still fail in the third try it will drag to the incorrect one and then it will highlight or just change the color doesn't matter to red.
If user drags to the correct one it will highlight green and replace QuestionMark with the correct one (I do not want to be draggable anymore that image)
------
Game 2 (is more or less the same...)
There's no QuestionMark column, there's only :
Skate
Swim
Archery
--------------
(Lot of answers)
Now the way to play is the same (about the fails and etc) the thing is now when I drag any answer to the correct one it won't replace the correct one, it will just disappear and if it fails instead of highlighting all the corrects one it will highlight the correct answer (for instance; if I drag wheel to Swim once, it doesn't happen anything just animate to the place where it was, if I do it twice it will highlight the Skate one, and if it fails at third one it just drag wherever he did and highlight with red)
I'm planning to build an app that does a simple check, I'm calling an endpoint and I'll get some params, and then I'll know how many ImageView are going to be displayed in the screen.
This is like a puzzle, and it would look like this :
So I have different options, which contains only one correct answer, I'm planing the way to achieve this, could be able to drag "Bow" to the questionmark infront of "skateboarding" and then says that is not correct, then drag it to the "archery" one and replace the questionmark for the ImageView from the bottom that contains the word "Arrow".
Layout should contain one column for Question (this should be the sports) then another one in front of the Question one and should be the Answer one, then below them should contain the Options one.
Was it clear? Otherwise let me know and I'll try to explain it a little bit with more details.
EDIT
What I thought is having like a class that contains a list of Answers or just create like :
RightList : (id:1,id:2,id:3)
LeftList : (id:1, id:2, id:3)
DownList : (Bow = id:2),(Skate = id:1), (Ball = id:3)
Then doing the drag and drop thing when the DragEvent.ACTION_DROP or DragEvent.ACTION_DRAG_ENDEDI do not know which one, check (Pseudocode below)
if(imageDragged.id==location.id) then replace the question mark image for imageDragged
else animate the image to the place where it comes
I do not know if creating a class that implements onDragListener() or something like that, I'd like to have it generic so I can use it on different games like for instance :
SKATE(id:1) ARCHERY(id:2) FOOTBALL(id:3)
Answers : TABLE(C.A. id:1) BOW(C.A. id:2) GRASS(C.A. id:3) GOAL(C.A. id:3) BALL(C.A. id:3) ARROW(C.A. id:2) AXES(C.A. id:1) WHEELS(C.A. id:1)
So if I drag and drop for instance BOW to FOOTBALL then it should display that is bad, otherwise say that it's good.
EXAMPLE 1/3
Just for reference and summarize everything. Here is one 100 lines code, within single Activity and imports, representing all this behavior even with simple animation.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bind()
}
private fun bind() {
addQuestions()
addAnswers()
}
#SuppressLint("InflateParams")
private fun addQuestions() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_question, null)
view.setOnDragListener(DragListener())
questionContainer.addView(view)
}
}
#SuppressLint("InflateParams")
private fun addAnswers() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_answer, null)
view.setOnTouchListener(DragItemTouchListener())
answerContainer.addView(view)
}
}
private inner class DragItemTouchListener : OnTouchListener {
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
return if (motionEvent.action == MotionEvent.ACTION_DOWN) {
dragMultiple(view)
true
} else {
false
}
}
private fun dragMultiple(view : View) {
val data = ClipData.newPlainText("", "")
val shadowBuilder = DragShadowBuilder(
view
)
val parent = view.parent as ViewGroup
view.startDragAndDrop(data, shadowBuilder, view, 0)
parent.removeView(view)
}
}
private inner class DragListener : OnDragListener {
override fun onDrag(v: View, event: DragEvent): Boolean {
when (event.action) {
DragEvent.ACTION_DRAG_STARTED -> {
}
DragEvent.ACTION_DRAG_ENTERED -> {
}
DragEvent.ACTION_DRAG_EXITED -> {
}
DragEvent.ACTION_DROP -> {
animateDropEffect(v as ViewGroup, event.localState as View)
}
DragEvent.ACTION_DRAG_ENDED -> {
}
else -> {
}
}
return true
}
private fun animateDropEffect(into: ViewGroup, view: View) {
into.addView(view)
val params = (view.layoutParams as FrameLayout.LayoutParams)
.apply {
gravity = Gravity.END
}
view.layoutParams = params
}
}
}
All Xmls used. Below xml for all examples below.
/* activity_main.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="#+id/mainContainer"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="500dp"
android:animateLayoutChanges="true">
<LinearLayout
android:id="#+id/questionContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<LinearLayout
android:id="#+id/answerContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</FrameLayout>
/* item_question.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:padding="5dp">
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="start"
android:background="#android:color/holo_blue_bright">
</View>
<View
android:id="#+id/questionView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="end"
android:background="#android:color/holo_orange_light">
</View>
</FrameLayout>
/* item_answer.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp"
android:tag="Test">
<LinearLayout
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:background="#android:color/darker_gray">
</LinearLayout>
</FrameLayout>
EXAMPLE 2/3
It's not a problem to make dragging for few elements following with a the same approach. Here is a little crappy, but simple example.
Modified code for second example. Xml stay the same.
class MainActivity : AppCompatActivity() {
var activeOneDrag : Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bind()
}
private fun bind() {
addQuestions()
addAnswers()
}
fun getRandomColor(): Int {
return Color.argb(255, Random.nextInt(255),
Random.nextInt(255), Random.nextInt(255))
}
#SuppressLint("InflateParams")
private fun addQuestions() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_question, null)
view.setOnDragListener(DragListener())
questionContainer.addView(view)
}
}
#SuppressLint("InflateParams")
private fun addAnswers() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_answer, null)
(view as ViewGroup).getChildAt(0).setBackgroundColor(getRandomColor())
view.setOnTouchListener(DragItemTouchListener())
answerContainer.addView(view)
}
}
private inner class DragItemTouchListener : OnTouchListener {
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
return if (motionEvent.action == MotionEvent.ACTION_DOWN) {
dragMultiple(view)
true
} else {
false
}
}
private fun dragMultiple(view : View) {
val parent = view.parent as ViewGroup
parent.removeView(view)
/**
* Some other logic with selective multiple View.
* Just getting neighbor in our case
*/
var anotherView : View? = null
if (!activeOneDrag) {
anotherView = parent.getChildAt(
parent.indexOfChild(view) + 1)
parent.removeView(anotherView)
}
activeOneDrag = !activeOneDrag
/**
* As you can see, there is postDelay here.
* But only for our case with animateLayoutChanges,
* with delays removing View! In your samples, you could remove it
* with listener on your own animation, if any!
*/
parent.postDelayed({
val layout = LinearLayout(this#MainActivity)
val params = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT)
params.gravity = Gravity.BOTTOM
layout.layoutParams = params
layout.orientation = LinearLayout.HORIZONTAL
layout.addView(view)
if (anotherView != null) {
layout.addView(anotherView)
}
layout.visibility = INVISIBLE
mainContainer.addView(layout)
parent.post {
layout.startDragAndDrop(
ClipData.newPlainText("", ""),
DragShadowBuilder(layout), layout, 0)
}
}, 400)
}
}
private inner class DragListener : OnDragListener {
override fun onDrag(v: View, event: DragEvent): Boolean {
when (event.action) {
DragEvent.ACTION_DRAG_STARTED -> {
}
DragEvent.ACTION_DRAG_ENTERED -> {
}
DragEvent.ACTION_DRAG_EXITED -> {
}
DragEvent.ACTION_DROP -> {
val view = event.localState as View
(view.parent as ViewGroup).removeView(view)
view.visibility = VISIBLE
animateDropEffect(v as ViewGroup, event.localState as View)
}
DragEvent.ACTION_DRAG_ENDED -> {
}
else -> {
}
}
return true
}
private fun animateDropEffect(into: ViewGroup, view: View) {
into.addView(view)
val params = (view.layoutParams as FrameLayout.LayoutParams)
.apply {
gravity = Gravity.END
}
view.layoutParams = params
}
}
}
EXAMPLE 3/3
As I see, it's not clear, how to change simple actions with animation or Drag listening area. Here is another simple example of doing all actions
class MainActivity : AppCompatActivity() {
#Volatile
var state : State = State.INACTIVE
enum class State {
ACTIVE, INACTIVE, HANDLED
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bind()
}
private fun bind() {
addQuestions()
addAnswers()
}
private fun getRandomColor(): Int {
return Color.argb(255, Random.nextInt(255),
Random.nextInt(255), Random.nextInt(255))
}
#SuppressLint("InflateParams")
private fun addQuestions() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_question, null)
view.findViewById<View>(R.id.questionView)
.setOnDragListener(DragListener())
questionContainer.addView(view)
}
}
#SuppressLint("InflateParams")
private fun addAnswers() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_answer, null)
(view as ViewGroup).getChildAt(0).setBackgroundColor(getRandomColor())
view.setOnTouchListener(DragItemTouchListener())
answerContainer.addView(view)
}
}
private inner class DragItemTouchListener : OnTouchListener {
val ITEM_INDEX_D = "Index-From"
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
return if (motionEvent.action == MotionEvent.ACTION_DOWN) {
createDrag(view)
true
} else {
false
}
}
private fun createDrag(view : View) {
val parent = view.parent as ViewGroup
view.tag = Pair(ITEM_INDEX_D,
parent.indexOfChild(view))
view.startDragAndDrop(ClipData.newPlainText("", ""),
DragShadowBuilder(view), view, 0)
parent.removeView(view)
parent.setBackgroundColor(Color.WHITE)
}
}
private inner class DragListener : OnDragListener {
override fun onDrag(parent: View, event: DragEvent): Boolean {
val view = event.localState as View
when (event.action) {
DragEvent.ACTION_DRAG_STARTED -> {
state = State.ACTIVE
}
DragEvent.ACTION_DRAG_ENTERED -> {
}
DragEvent.ACTION_DRAG_EXITED -> {
}
DragEvent.ACTION_DROP -> {
state = State.HANDLED
animateDropEffect(parent, view)
return true
}
DragEvent.ACTION_DRAG_ENDED -> {
if (state == State.ACTIVE) {
state = State.INACTIVE
animateMoveBack(view,
(view.tag as Pair<*, *>).second as Int)
}
return true
}
else -> {
}
}
return true
}
private fun animateMoveBack(view: View, index : Int) {
answerContainer.addView(view, index)
}
private fun animateDropEffect(into: View, view: View) {
val parent = (into.parent as ViewGroup)
parent.addView(view)
val params = (view.layoutParams as FrameLayout.LayoutParams)
.apply {
gravity = Gravity.END
}
view.layoutParams = params
checkIsCorrect(parent)
}
private fun checkIsCorrect(parent : ViewGroup) {
val correct = Random.nextBoolean()
val colorFrom = Color.WHITE
val colorTo : Int = if (correct) 0x8000ff00.toInt() else 0x80ff0000.toInt()
ObjectAnimator.ofObject(
parent,
"backgroundColor",
ArgbEvaluator(),
colorFrom,
colorTo
)
.setDuration(1000)
.start()
}
}
}
UPDATE
The last update from the comments sections. I think it's enough, and of course you would need you changes. So just change two "if" statement to align with your requirements and animation.
class MainActivity : AppCompatActivity() {
enum class State {
ACTIVE, INACTIVE, HANDLED
}
var state : State = State.INACTIVE
var failsCount = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bind()
}
private fun bind() {
addQuestions()
addAnswers()
}
private fun getRandomColor(): Int {
return Color.argb(255, Random.nextInt(255),
Random.nextInt(255), Random.nextInt(255))
}
#SuppressLint("InflateParams")
private fun addQuestions() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..3) {
val view = inflater.inflate(R.layout.item_question, null)
view.findViewById<View>(R.id.questionView)
.setOnDragListener(DragListener())
questionContainer.addView(view)
}
}
#SuppressLint("InflateParams")
private fun addAnswers() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..3) {
val view = inflater.inflate(R.layout.item_answer, null)
(view as ViewGroup).getChildAt(0).setBackgroundColor(getRandomColor())
view.setOnTouchListener(DragItemTouchListener())
answerContainer.addView(view)
}
}
private inner class DragItemTouchListener : OnTouchListener {
val ITEM_INDEX_D = "Index-From"
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
return if (motionEvent.action == MotionEvent.ACTION_DOWN) {
createDrag(view)
true
} else {
false
}
}
private fun createDrag(view : View) {
val parent = view.parent as ViewGroup
view.tag = Pair(ITEM_INDEX_D,
parent.indexOfChild(view))
view.startDragAndDrop(ClipData.newPlainText("", ""),
DragShadowBuilder(view), view, 0)
parent.removeView(view)
parent.setBackgroundColor(Color.WHITE)
}
}
private inner class DragListener : OnDragListener {
val ANIM_DURATION_LONG = TimeUnit.SECONDS.toMillis(1)
val ANIM_DURATION_SHORT = TimeUnit.MILLISECONDS.toMillis(500)
val GREEN_ALPHA = 0x8000ff00.toInt()
val RED_ALPHA = 0x80ff0000.toInt()
val ANIM_COLOR = "backgroundColor"
override fun onDrag(parent: View, event: DragEvent): Boolean {
val view = event.localState as View
when (event.action) {
DragEvent.ACTION_DRAG_STARTED -> {
state = State.ACTIVE
}
DragEvent.ACTION_DRAG_ENTERED -> {
}
DragEvent.ACTION_DRAG_EXITED -> {
}
DragEvent.ACTION_DROP -> {
state = State.HANDLED
animateDropEffect(parent, view)
return true
}
DragEvent.ACTION_DRAG_ENDED -> {
if (state == State.ACTIVE) {
state = State.INACTIVE
animateMoveBack(view,
(view.tag as Pair<*, *>).second as Int)
}
return true
}
else -> {
}
}
return true
}
private fun animateMoveBack(view: View, index : Int) {
answerContainer.addView(view, index)
}
private fun animateDropEffect(into: View, view: View) {
val parent = (into.parent as ViewGroup)
parent.addView(view)
val params = (view.layoutParams as FrameLayout.LayoutParams)
.apply {
gravity = Gravity.END
}
view.layoutParams = params
checkIsCorrect(parent)
}
private fun checkIsCorrect(parent : ViewGroup) {
val correct = false
if (correct) {
animateColorChange(parent, true)
return
}
if (++failsCount > Companion.MAX_FAIL_COUNT) {
animateColorChange(parent, false)
return
}
animateWrongAttempt(parent)
}
private fun animateWrongAttempt(parent: ViewGroup) {
val questionMark = parent.findViewById<View>(R.id.questionView)
questionMark.setBackgroundColor(Color.RED)
val va = ValueAnimator.ofFloat(1f, 1.1f)
va.interpolator = BounceInterpolator()
va.duration = ANIM_DURATION_SHORT
va.addUpdateListener { animation ->
questionMark.scaleX = animation.animatedValue as Float
questionMark.scaleY = animation.animatedValue as Float
}
va.start()
}
private fun animateColorChange(parent : ViewGroup, right : Boolean) {
val colorFrom = Color.WHITE
ObjectAnimator
.ofObject(parent, ANIM_COLOR,
ArgbEvaluator(), colorFrom,
if (right) GREEN_ALPHA else RED_ALPHA)
.setDuration(ANIM_DURATION_LONG)
.start()
}
}
companion object {
const val MAX_FAIL_COUNT = 2
}
}
And new xml.
/* activity_main.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="#+id/mainContainer"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="500dp"
android:animateLayoutChanges="true">
<LinearLayout
android:id="#+id/questionContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<LinearLayout
android:id="#+id/answerContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</FrameLayout>
/* item_question.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:paddingTop="10dp">
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="start"
android:layout_margin="5dp"
android:background="#android:color/holo_blue_bright">
</View>
<View
android:id="#+id/questionView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="end"
android:layout_margin="5dp"
android:background="#android:color/holo_orange_light">
</View>
</FrameLayout>
/* item_answer.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp"
android:tag="Test">
<LinearLayout
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:background="#android:color/darker_gray">
</LinearLayout>
</FrameLayout>
I think, there are various ways to do it. For me, seems nice, to split the screen into two main sections. a) To have single vertical list with Items and connections to "Question mark", refer to the image below.
Single row in a vertical list
So it's easy to change size to the item list, which you are retrieving from Server, by dynamically adding and removing simple View. Once correct tile would be places on the top of "Question mark" you will change new View on the top of this View. b) To create bottom list with possible "Answers" there will be Horizontal list, with this items. As you mentioned in our question.
Bottom horizontal list
Description
a) I really do't like implementation of such kind of task with Android widgets like RecyclerView. With small list of items, we could bring a lot of customization. For the first list I would use VerticalScrollView with LinearLayout and View (with any your layout).
Note. Your last layout could be ConstraintLayout. Ex. 1. In this case your dragged items would stick on exact places, where you live it. And In case it's stick on specific row with more than half size, you could change view for it (to green or red). Ex 2. Maybe even better way would be animate moving from place where live the tiles, to the nearest "Question mark" with "Property Animation"
b) To create bottom list I would use the same structure with `HorizontalListView". This will help to use "Property Animation" and not just animate dragging, but also move an object with your grading trajectory. Using dynamically added items in the containers with simple views will reflect on changes in bottom, or top lists.
Implementation
1) Initial listeners for each your "Answer" tiles.
// Assign the touch listener to your view which you want to move
findViewById(R.id.myimage1).setOnTouchListener(new MyTouchListener());
// This defines your touch listener
private final class MyTouchListener implements OnTouchListener {
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
ClipData data = ClipData.newPlainText("", "");
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(
view);
view.startDrag(data, shadowBuilder, view, 0);
view.setVisibility(View.INVISIBLE);
return true;
} else {
return false;
}
}
}
2) Define each of your target Views in the top list.
findViewById(R.id.bottomright).setOnDragListener(new MyDragListener());
class MyDragListener implements OnDragListener {
Drawable enterShape = getResources().getDrawable(
R.drawable.shape_droptarget);
Drawable normalShape = getResources().getDrawable(R.drawable.shape);
#Override
public boolean onDrag(View v, DragEvent event) {
int action = event.getAction();
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
// do nothing
break;
case DragEvent.ACTION_DRAG_ENTERED:
v.setBackgroundDrawable(enterShape);
break;
case DragEvent.ACTION_DRAG_EXITED:
v.setBackgroundDrawable(normalShape);
break;
case DragEvent.ACTION_DROP:
// Dropped, reassign View to ViewGroup
View view = (View) event.getLocalState();
ViewGroup owner = (ViewGroup) view.getParent();
owner.removeView(view);
LinearLayout container = (LinearLayout) v;
container.addView(view);
view.setVisibility(View.VISIBLE);
break;
case DragEvent.ACTION_DRAG_ENDED:
v.setBackgroundDrawable(normalShape);
default:
break;
}
return true;
}
}
3) That is it you need to create dragging. Just to make another setup in the Activity and View changes.
However you could check few other examples of drag and drop implementation. But since you were using simple Views and Scrollable containers, you can align every sample to work with your lists.

Dynamically adding view or inflating layout to RecyclerView's item

I am working on a social network application in which I need to show feeds to subscribers.. for showing feeds in a list. I am using RecyclerView. No Problem till now... but the problem comes when I have to show some comments on the feed. I am trying to accomplish a layout similar to Instagram where they show an image and maximum 3-4 comments with it in the main list. I am getting Image links and its comments (in JSON array form) from server.. sometimes I get comment array size 0 and sometime 1/2../10. I have created an XML for the comment layout that contains 2 TextView and I am inflating it in FeedViewHolder based on my ViewType.. my item_comment look like
<RelativeLayout
android:id="#+id/comment_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:paddingTop="5dp">
<TextView
android:textStyle="bold"
android:textColor="#color/indigo_500"
android:id="#+id/comment_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:src="#drawable/profile_pic"
android:text="Somesh Kumar"/>
<TextView
android:id="#+id/comment_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="#+id/comment_name"
android:ellipsize="end"
android:paddingLeft="5dp"
android:singleLine="true"
android:text="This is a comment"/>
</RelativeLayout>
I am passing my feed list to RecyclerView adaptor..and then I am checking if the item (that will be shown) should have the comments or not.
#Override
public int getItemViewType(int position)
{
FeedParser.DataClass dataClass = feedList.get(position);
int ITEM_TYPE = 0;
if (dataClass.getPost_type() == FEED_TYPE_PICTURE) // Feed item contains caption with pictures
{
// check if it has comments
if (dataClass.getComments().size() == 0)
{
ITEM_TYPE = PICTURE_WITHOUT_COMMENTS;
}
else
{
ITEM_TYPE = PICTURE_WITH_COMMENTS;
}
}
return ITEM_TYPE;
}
and passing ITEM_TYPE to onCreateViewHolder like
#Override
public FeedViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_feed, parent, false);
return new FeedViewHolder(itemView, viewType);
}
and in FeedViewHolder inflating item layout and comment layout if viewType has comments
public FeedViewHolder(View itemView, int viewType)
{
super(itemView);
tvFeedUsername = (TextView) itemView.findViewById(R.id.tvFeedUsername);
tvFeedCaption = (TextView) itemView.findViewById(R.id.tvFeedCaption);
switch (viewType)
{
// TODO: Write code for other feed type such as picture and video
// viewType 1 = PICTURE_WITH_COMMENTS
case 1:
layoutComments = (LinearLayout) itemView.findViewById(R.id.layoutComments);
commentView = layoutInflater.inflate(R.layout.item_comment, null);
postCommentText = (TextView) commentView.findViewById(R.id.comment_text);
postCommentName = (TextView) commentView.findViewById(R.id.comment_name);
layoutComments.addView(commentView);
break;
}
}
I have verified this works perfect but when i setText for holder.postCommentName & holder.postCommentText in onBindViewHolder it only set text for last comment ( i know it's because i am looping through all the comment and setting them one by one on the same holder.postCommentText and holder.postCommentName here is my onBindViewHolder..
#Override
public void onBindViewHolder(FeedViewHolder holder, int position)
{
FeedParser.DataClass feedModel = feedList.get(position);
holder.tvFeedUsername.setText(feedModel.getName());
holder.tvFeedCaption.setText(feedModel.getPost_status());
if (getItemViewType(position) == TEXT_WITH_COMMENTS)
{
for (int commentNum = 0; commentNum < feedModel.getComments().size(); commentNum++)
{
holder.postCommentName.setText(feedModel.getComments().get(commentNum).getUser_name());
holder.postCommentText.setText(feedModel.getComments().get(commentNum).getComment());
}
}
}
I don't know even if this is the right approach or not... but this is how some people wrote an answer here... like This one . I have searched many SO post but not getting what I want. Is there any other way where I can inflate comment's XML layout and add it multiple time on RecyclerView 's item?
Thanks any help would be much appreciated!
i Believe you need to create a nested recycler view in the main recycler view and you need to pass the data into the nested adapter as that of the comments .So basically
1.)Main Recycler View contains Caption , Image , RecyclerView for comments (as per instagram)
2.)In the second reyclerview put your above comment layout and create a seperate adapter with data as comments
Thats It .. Ask me for any further help

Lollipop AppBarLayout/Toolbar missing overscroll animation

using the most basic example with AppBarLayout and Toolbar, I cannot see the overscroll animation (the glow from bottom nor top) when trying to scroll more. However, if you fling the content, it will show it.
Here is the code (nav_drawer_toolbar_layout.xml):
<android.support.design.widget.CoordinatorLayout
xmlns:android="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">
<!-- Replace fragments in this content frame, like a RecycleView -->
<FrameLayout
android:id="#+id/content_frame"
app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:minHeight="?attr/actionBarSize"
app:titleTextAppearance="#style/Base.TextAppearance.AppCompat.Title"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways"/>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
Followed by simple Activity class:
public class MyActivity extends AppCompatActivity implements {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.nav_drawer_toolbar_layout);
// Setup the toolbar/actionbar
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FragmentManager manager = getFragmentManager();
manager.beginTransaction().replace(R.id.content_frame, new MyFragmentList).commit();
}
}
MyFragmentList is a fragment with a RecycleView with content to scroll the app.
However if I remove AppBarLayout from the xml and leave Toolbar open (just comment AppBarLayout opening and closing), it will show the overscroll animation (the glow) when scrolling.
Or if you remove layout_scrollFlags="scroll" then the overscroll works but you can't get the actionbar to hide when you scroll.
For extra information, debugging RecycleView, line 2272
if(this.mBottomGlow != null && !this.mBottomGlow.isFinished()) {
is always finished when including AppBarLayout and not finished when it is not there. Is something over-writing its touch events?
Does anyone know who to show overscroll animation (glow) with AppBarLayout?
EDIT: There seems to be a ticket for this bug. You could definintely do what artur.dr...#gmail.com did and extend RecyclerView to override RecyclerView#dispatchNestedScroll to always return false (he writes true in his report) you can get overscroll animations working, though i'm pretty sure it might break something down the line.
Unfortunately how RecyclerView is coded and how NestedScrollingChild API is made there is no clean way to have the desired behavior.
This is from RecyclerView (23.1.1, however I do not believe any version before it fixes the issue) inside the method scrollByInternal.
if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
// Update the last touch co-ords, taking any scroll offset into account
mLastTouchX -= mScrollOffset[0];
mLastTouchY -= mScrollOffset[1];
if (ev != null) {
ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
}
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
} else if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {
if (ev != null) {
pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
}
considerReleasingGlowsOnScroll(x, y);
}
As we can see here on the javadoc for dispatchNestedScroll (part of the NestedScrollingChild API) as long as there is one parent that consumes the scroll, RecyclerView will not apply any overscroll animation (edge glow).
AppBarLayout does consume scrolling, more specifically as long as there is a NestedScrollingParent that returns true on onStartNestedScroll, overscroll animations will not happen.
CoordinatorLayout is a NestedScrollingParent but does not return true unless there is a CoordinatorLayout.Behavior that does. AppBarLayout's default behavior does implement this methdo to return true when there is vertical scrolling + AppBarLayout has something to scroll + view is big enough to scroll.
// Return true if we're nested scrolling vertically, and we have scrollable children
// and the scrolling view is big enough to scroll
final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
&& child.hasScrollableChildren()
&& parent.getHeight() - directTargetChild.getHeight() <= child.getHeight();
Flinging takes a slightly different approach, allowing the overscroll animation happening regardless if the NestedScrollingParent is consuming the scrolling.
if (!dispatchNestedPreFling(velocityX, velocityY)) {
final boolean canScroll = canScrollHorizontal || canScrollVertical;
dispatchNestedFling(velocityX, velocityY, canScroll);
if (canScroll) {
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
mViewFlinger.fling(velocityX, velocityY);
return true;
}
}
Honestly I cannot tell if this is a bug because both logic make sense. If you are scrolling to the top part of the view, and you have something akin to a CollapsingToolbar, you wouldn't want an overscoll animation to happen. However there is a way to make it so that the behavior can consume the x/y amount of scrolling to stop the animation from happening. It is also weird that both code for scrolling and flinging is different.

Transfer scroll event from sliding view to ScrollView - Sliding panel with ScrollView like Google Maps

So I'm using the Sliding Up Panel Library in my application, and I'm trying to implement a ScrollView inside the sliding panel. Since both the sliding panel and the ScrollView are controlled by vertical scrolls, this is causing me some issues.
I've partially got it to work by switching the panel's dragview once the panel has been slid all the way up, and when the ScrollView has been scrolled to the top.
The problem I'm facing now is that, when scrolling the panel to top the scrolling doesn't transfer to the ScrollView, like it does in Google Maps. Little hard to explain, so look at the video here:
www.youtube.com/watch?v=9MUsmQzusX8&feature=youtu.be
This is the panel slide listener:
...
slidePanel.setEnableDragViewTouchEvents(true);
slidePanel.setPanelSlideListener(new SlidingUpPanelLayout.PanelSlideListener() {
#Override
public void onPanelSlide(View panel, float slideOffset) {
// Change the dragview to panelheader when panel is fully expanded
// I'm doing this here instead of in onPanelExpanded,
// because onPanelExpanded first gets called once scroll
// is released.
if (slideOffset <= 0) {
slidePanel.setDragView(layoutPanelTop);
}
// If the panel is not fully expanded set the whole
// panel as dragview
else if(slideOffset > 0) {
slidePanel.setDragView(layoutPanel);
}
}
}
#Override
public void onPanelExpanded(View panel) {
// layout.setDragView(layoutPanelTop);
panelCollapsed = false;
panelExpanded = true;
panelAnchored = false;
Log.v("TAG, "panelExpanded");
}
#Override
public void onPanelCollapsed(View panel) {
slidePanel.setDragView(layoutPanel);
panelCollapsed = true;
panelExpanded = false;
panelAnchored = false;
Log.v(TAG, "panelCollapsed");
}
#Override
public void onPanelAnchored(View panel) {
slidePanel.setDragView(layoutPanel);
panelCollapsed = false;
panelExpanded = false;
panelAnchored = true;
Log.v(TAG, "panelAnchored");
}
});
And I have managed to create a fully working scrollview listener by extending scrollview, which can detect scroll direction and onDown and onUp motion events:
private boolean atScrollViewTop = false;
#Override
public void onScrollChanged(int scrollY) {
scrollY = Math.min(mMaxScrollY, scrollY);
if (scrollY <= 0) {
Log.v("myTag", "You at scrollview top");
atScrollViewTop = true;
} else {
atScrollViewTop = false;
}
mScrollSettleHandler.onScroll(scrollY);
switch (mState) {
case STATE_SCROLL_UP:
if (panelExpanded && atScrollViewTop) {
slidePanel.setDragView(layoutPanel);
} else {
slidePanel.setDragView(layoutPanelTop);
}
Log.v("myTag", "scrolling up");
break;
case STATE_SCROLL_DOWN:
slidePanel.setDragView(layoutPanelTop);
Log.v("myTag", "scrolling down");
break;
}
}
#Override
public void onDownMotionEvent() {
}
#Override
public void onUpOrCancelMotionEvent() {
}
I've been struggling with this the last two days.. So really hope on some pointer at least. Thanks very much in advance. Regards Jakob Harteg.
Sorry for delay.. i find the solution.
image = (ImageView) findViewById(R.id.image); //Layout to slide
SlidingUpPanelLayout layout = (SlidingUpPanelLayout)
findViewById(R.id.sliding_layout);
layout.setDragView(image);
/*This method sets the layout to be used only
in the sliding panel, while all layouts children
are able to perform other functions such as scrolling */
And this is the layout
<..SlidingUpPanelLayout
android:id="#+id/sliding_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center" />
<LinearLayout
android:id="#+id/slide_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="35dp"
android:background="#drawable/ec_image"/>
<!-- FINALLY SCROLLVIEW -->
<ScrollView .... />
Hope it is useful.
I'm guessing ScrollView is child of the SlidingPanel?
In that case, override onInterceptTouchEvent to your SlidingPanel to intercept the onTouch event of your ScrollView when y = 0.
onInterceptTouchEvent does the following two:
child gets action cancel event
parent get the event trough onTouch
I don't know if I've arrived to late but after working hard some days I've found that AndroidSlidingUp panel has a method called setScrollView who handles scroll events properly.
I hope that this post will be useful because I was spending much time searching and I didn't find some tip that help me.

How to get dialog size?

I am looking for a way to get size of a custom dialog. I went through this question, but the only answer given is pretty useless, because if I try mDialog.getWindow().getAttributes().height; it only returns -2, which is a constant for WRAP_CONTENT attribute which I set to dialog. How can I get the size of it. I want to know the siye for the background image.
Give it a try:
mDialog.getWindow().getDecorView().getHeight()
Actually, in Android it doesn't work like in iOS - you can't get the size of the View itself, what you can do, though, is to ask for the size of the ROOT layout of that view.
e.g.:
myDialog.this.findViewById(R.id.dialog_root_layout).getHeight());
#Kormilsev Anatoliy has answered correct and I am just improving.
So in the class you inherit from Dialog class just override the method:
#Override
public void onWindowFocusChanged (boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
height = getWindow().getDecorView().getHeight();
}
In case, if you have your own XML layouts for custom dialog.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/dialog_main_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="#color/primaryBackground">
/* whatever you want here */
</android.support.constraint.ConstraintLayout>
In activity:
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.popup_gameover);
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
#Override
public void onShow(DialogInterface d) {
View view = dialog.findViewById(R.id.dialog_main_layout);
int width = view.getWidth();
int height = view.getHeight();
...
}
});
This width and height exacly as expected.

Categories

Resources