Android scrolling on a HorizontalScrollView - android

I have a HorizontalScrollView with a LinearLayout inside.
This LinearLayout is populated (dynamically) with a lot of TextViews.
I want to scroll my HorizontalScrollView to a specified TextView depending on some index.
I try this but doesn't work
while (!this.stopsCursor.isAfterLast()) {
int index = this.stopsCursor.getInt(this.stopsCursor.getColumnIndex("index"));
if (index == session.getServiceStopIndex()) {
TextView view = (TextView) this.linearLayout.getChildAt(index);
StyleHelper.setStopHighlight(view);
Log.v("TEST", "Left : " + view.getLeft() + " Right : " + view.getRight());
int offsetX = ((view.getLeft() + view.getRight()) / 2);
this.horizontalScrollView.scrollTo(offsetX, 0);
}
this.stopsCursor.moveToNext();
}
My logger show me Left : 0 Right : 0
Can someone help me?
Thanks in advance!

The problem is probably that you are attempting to get the left and right of the view before layout. You need to move this code later in the layout flow. It's hard to say exactly where to move it without more context as to when you want to scroll, but if it's immediately after creation (and I assume that you're populating the LinearLayout somewhere around the Activity's onCreate) then you might want to look at overriding View.onSizeChanged and doing it around then.

Related

Expandable RecyclerView With Different Details

I've made an RecyclerView which is expandable , my expanded items in RecyclerView have different counts and it's not possible to set a single layout for them.
My program compares the price of some services ( 6 different services for now ) and every service has a different count of sub services which that count will be passed to RV Adapter.
I want somethings like this :
different expanded item counts
I've tried to solve it with these solutions :
FIRST SOLUTION :
my RV data model has a int variable named to serviceCount and gets data from MainActivity for each type of service, my layout should repeat as serviceCount size , I've written this code in onBindViewHolder :
if (holder.detailLayout.getVisibility() == View.VISIBLE) {
for (int i = 0; i < priceList.get(position).getServiceCount(); i++) {
// Code
}
I'm trying to create a layout programmatically and repeat it as that size which is something like this :
for (int i = 0; i < priceList.get(position).getServiceCount(); i++) {
ConstraintLayout newDetailLayout = new ConstraintLayout(context);
ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.MATCH_PARENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT);
layoutParams.topToBottom = R.id.tv_pricedetail_service;
layoutParams.rightToRight = 0;
layoutParams.leftToLeft = 0;
layoutParams.setMargins(0,margin8dp*i*6,0,0);
Button requestButton = new Button(context);
requestButton.setId(View.generateViewId());
requestButton.setText("درخواست" + " " + String.valueOf(i));
ConstraintLayout.LayoutParams requestButtonParams = new ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT);
requestButtonParams.leftToLeft = 0;
requestButtonParams.topToTop = 0;
requestButtonParams.setMargins(margin8dp *4,margin8dp *2,0,0);
newDetailLayout.addView(requestButton, requestButtonParams);
TextView serviceName = new TextView(context);
serviceName.setId(View.generateViewId());
serviceName.setText("تست" + " " + String.valueOf(i));
ConstraintLayout.LayoutParams serviceNameParams = new ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT);
serviceNameParams.topToTop = 0;
serviceNameParams.rightToRight = 0;
serviceNameParams.baselineToBaseline = requestButton.getId();
serviceNameParams.setMargins(0,margin8dp *2,margin8dp *4,0);
newDetailLayout.addView(serviceName, serviceNameParams);
TextView serviceCost = new TextView(context);
serviceCost.setText("هزینه" + " " + String.valueOf(i));
ConstraintLayout.LayoutParams serviceCostParams = new ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT);
serviceCostParams.leftToRight = requestButton.getId();
serviceCostParams.rightToLeft = serviceName.getId();
serviceCostParams.baselineToBaseline = requestButton.getId();
newDetailLayout.addView(serviceCost, serviceCostParams);
holder.detailLayout.addView(newDetailLayout, layoutParams);
}
//Toast.makeText(context, String.valueOf(priceList.get(position).getServiceCount()), Toast.LENGTH_SHORT).show();
}
output of my code is this : output view BUT when user expand the first item the other items in expanded view copy the first item expanded detail ! and I should create different layout for every expanded layout.
SECOND SOLUTION:
I've made 6 different layout for each service ( they will be more in future ) and inflate them in onCreateViewHolder with instantiated variables
is this right for doing something like this ? or I can do something better ?
EDIT :
onBindViewHolder Codes :
public void onBindViewHolder(#NonNull itemsViewHolder holder, int position) {
// Init Layout
final priceItemDM items = priceList.get(position);
holder.iv_logo.setImageDrawable(items.getLogo());
holder.txt_name.setText(items.getName());
holder.txt_price.setText(items.getPrice());
// Expand & Collapse Mode
final boolean isExpanded = position == mExpandedPosition;
final int positionNo = position;
holder.detailLayout.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!items.getPrice().equals(receivingData) && !items.getPrice().equals(receiverError)) {
mExpandedPosition = isExpanded ? -1 : positionNo;
notifyItemChanged(positionNo);
} else {
Toast.makeText(context, "اطلاعات ناقص است ، لطفا مجددا تلاش فرمایید", Toast.LENGTH_SHORT).show();
}
}
});
if (!items.getPrice().equals(receivingData)) {
holder.pb_loading.setVisibility(View.INVISIBLE);
if (holder.detailLayout.getVisibility() == View.VISIBLE &&
!items.getPrice().equals(receiverError)) {
//priceList.get(position).getServiceNames().size()
holder.detailLayout.removeAllViews();
holder.detailLayout.addView(childView);
}
}
}
my expanded items in RecyclerView have different counts and it's not possible to set a single layout for them.
In your case it actually is possible to use a single layout for all items because they all look the same. They all can be expanded in the same way and their contents always look the same - only the amount of child items is different, but it doesn't mean you can't use one layout for them.
You should have two XML layout files - one for the expandable item, and one for the inner child row (the one that has a button).
The first solution is correct, you can't go with the second one. Creating a new layout every time makes no sense because your project will quickly turn into a mess due to the amount of files and the code that inflates them. Although the first solution doesn't have to be that complicated. I see that you are configuring all views in runtime - it would look much simpler if you do it in XML and just inflate the view when needed.
when user expand the first item the other items in expanded view copy the first item expanded detail ! and I should create different layout for every expanded layout.
I'm not sure I get your point but the approach is correct. The only thing is that you have to keep in mind this is a RecyclerView which reuses its child views when you scroll.
Example:
You expand item#1 and inflate 5 child rows in it, then you scroll. If item#4 is also expanded the recycler view will reuse item#1 when showing item#4, i.e. item#4 will automatically get 5 child rows even if it shouldn't have that many.
That means you have to clean up the child rows every time in onBindViewHolder to make sure you don't display information from the previous item. You will get rid of the problem if your onBindViewHolder always returns correct representation of a view for the given position. If you forget to clean up some reused views, you might see duplicated information while you scroll. If this is not really clear, please read how the ViewHolder pattern works, it's pretty simple once you get used to it.
Good luck!

Android View with a translate animation - positioning is not working as expected

I have a View that has an OnClickListener. When clicked, the view translates up to a certain position on the page. This is no problem, the view goes where it should. When the view is clicked again, I would like to position it somewhere else, but this is not the case. After a little bit of trouble shooting, I found that my View's getTop() method returns the same value - even after the translation animation has moved the view to a different part of the screen. For the second animation, it is not using the current position (as I would expect), it instead uses the initial position.
Few things that I am doing: I am using the ObjectAnimation class rather than the TranslationAnimation class, since I wanted to keep the OnClickListener functioning. With the TranslationAnimation class, I found that the view was correctly moved, but the OnClickListener was only working in the area that the View started from. Using the ObjectAnimation class, I was able to easily get the translation to work AND the OnClickListener functions correctly - it is triggered where the view currently is on the screen.
Here's what I have so far:
final LinearLayout child = layouts.get(i); //ArrayList containing some dynamic layouts
final int offset = target - child.getTop();
ObjectAnimator anim = ObjectAnimator.ofFloat(child,"translationY",offset);
anim.setDuration(250);
anim.start();
This is what happens when the view is clicked the first time. It translates up along the Y axis, where the offset determines how far the View needs to move from its current position.
Now, here's what happens on the second click. The goal here was to align the view with the parent's base.
target = parent.getBottom();
offset = target - child.getTop();
anim = ObjectAnimator.ofFloat(child, "translationY",offset);
anim.setDuration(250);
anim.start();
prev = child;
This is where things fall apart - child.getTop() returns the Y coordinate of the view's ORIGINAL position. Not the current position. So after the animation, the view is placed well below the bottom of the parent. I read a different question which stated that I should use child.getY() instead, which is supposed to give me the translationY position plus the top position, but this didn't lead to any better results. I can't seem to get this to work just right. I'd simply like to move the view from its current position to the bottom of the screen, but this appears to be a hard thing to accomplish. Any help would be appreciated.
EDIT
I have added an animation listener:
ObjectAnimator anim = ObjectAnimator.ofFloat(child,"translationY",offset);
anim.setDuration(250);
anim.addListener(new ObjectAnimator.AnimatorListener(){
#Override
public void onAnimationStart(Animator animation) {
System.out.println("start: " + child.getTop() + " " + child.getY());
}
#Override
public void onAnimationEnd(Animator animation) {
System.out.println("end: " + child.getTop() + " " + child.getY() + " " + child.getTranslationY());
child.setTop((int)child.getY());
System.out.println(child.getTop());
}
#Override
public void onAnimationCancel(Animator animation) {}
#Override
public void onAnimationRepeat(Animator animation) {}
});
anim.start();
Here I am setting the listener to try to change where the Top of the view is located. Behaviour is again not working as expected. The view is actually sent up above the screen when I do this. Output of the System.out looks like this:
start: 2008 2008.0
end: 2008 478.0 -1530.0
478
So calling child.getTop() after the animation is complete and setting a new position returns a positive integer, but the view is not actually completely on screen. It is above the screen, partly visible. The height of the view itself is about 700px. I am still so confused as to why this is such a hard thing to accomplish.
EDIT 2
I have also tried setting layoutparams inside the onAnimationEnd method:
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)child.getLayoutParams();
params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
params.topMargin = (int)child.getY();
child.setLayoutParams(params);
Result: child.getTop() still returns the original position of 2008.
You can get the very bottom of the screen coordinates like this :
float bottomOfScreen = getResources().getDisplayMetrics().heightPixels;
but you probably want it minus the height of your LinearLayout or else your LinearLayout will be cut off by the bottom :
float bottomOfScreen = getResources().getDisplayMetrics().heightPixels
- child.getHeight();
// if you want a little more space to the bottom
// try something like - child.getHeight()*2;
Then use ViewPropertyAnimator to animate your LL like this :
child.animate()
.translationY(bottomOfScreen)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setDuration(250);
The Interpolator is just to make the animation more realistic.
In the case that child.getHeight() returns 0 , your Linear Layout has not been finished setting up by the system, in that case you might want to do something like :
child.post(new Runnable() {
#Override
public void run() {
float bottomOfScreen = getResources().getDisplayMetrics().heightPixels
- child.getHeight()*2;
child.animate()
.translationY(bottomOfScreen)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setDuration(250);
}
});
Remember that a duration of 250 milliseconds is very fast, and does usually not look cool translating stuff on the screen, so you might want to set it a little higher, but thats just a matter of taste.

Android: call requestLayout() after update v.layout parameter causes layout reverts back to it's original position

I have framelayout which contains two relative layouts, one is on top of the other. When user clicks a button, the one on the top move 80% off the screen to the right. Then one on the bottom becomes clickable. This is what it looks like.
FrameLayout
RelativeLayout (bottom) RelativeLayout (top)
FilterWidgets Open/close button, ListView
It's really easy to achieve on 3.0+ with the new animation api which is Property base Animation. For the pre 3.0, because animation is view based. So I end up manually modify the layout property on onAnimationEnd. The call requestLayout to make it permanent, but only to find out the layout reverts back to original position. Anybody know how to move layout permanently?
see my other post if you want to see the whole picture:
https://stackoverflow.com/questions/14541265/changecursor-cause-layout-container-of-the-listview-to-reposition
theTranslationX.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator nullPointer) {
v.clearAnimation();
int theL = isMenuOn() ? 0 : v.getLeft() + getFilterWidth();
int theR = isMenuOn() ? v.getWidth() : v.getLeft() + getFilterWidth() + v.getWidth();
int theHeight = v.getHeight();
int theT = 0;
v.layout(theL, theT, theR, theHeight);
v.requestLayout();
}
});
This is 9 months late but try using:
yourView.layout(left,top,right,bottom); //all parameters are type int
However I don't think this is permanent, the position of the view will still be reset when you call requestLayout(), but give it a try.

Robotium : I want to scroll left on my Home screen top gallery section

Please look into the source code below :
public void test_Horizontalscroll() throws Exception {
solo.sleep(3000);
Gallery feature= (Gallery) solo.getView(R.id.featured_gallery);
solo.clickOnView(feature);
solo.scrollToSide(Solo.LEFT) ;
solo.sleep(10000);
}
The issue is that the entire screen is not horizontally scrollable, but only the Top section which is of type Gallery ( android.widget.Gallery)
Please help me.
// Scroll Left // Drag Right
fromX = (screenWidth/2) - (screenWidth/3);
toX = (screenWidth/2) + (screenWidth/3);
fromY = screenHeight/2;
toY = screenHeight/2;
solo.drag(fromX, toX, fromY, toY, 1);
Log.d(TAG,"Scroll Left");
i hope this code will help you.
you can use solo.drag() method.
Display d =((WindowManager)activity.getApplication().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
solo.drag(d.getWidth() / 2 , 0, 10 , 10 , 1);
just change the drag params to your needs.
I think the best thing to use is:
solo.scrollViewToSide(solo.getView(R.id.featured_gallery, solo.RIGHT);
You replace the R.id.featured_gallery with the resource id of the view or element (as they're all views anyway) that you're trying to scroll and use either
solo.RIGHT
or
solo.LEFT
to scroll the element. Hope it helps.

Android ViewFlipper + homescreen animation

I am trying to use a ViewFlipper and make it act like the home screen(The layout will move with your finger). Check out this for an example. I want to do this with a ViewFlipper that only contains two children so the opposite view should be shown on either side of the current view depending on which way the user moves their finger. This code works but only for 1 direction at a time. This is in onTouchEvent.
case MotionEvent.ACTION_MOVE:
leftView.setVisibility(View.VISIBLE);
rightView.setVisibility(View.VISIBLE);
// move the current view to the left or right.
currentView.layout((int) (touchEvent.getX() - oldTouchValue),
currentView.getTop(),
(int) (touchEvent.getX() - oldTouchValue) + 320,
currentView.getBottom());
// place this view just left of the currentView
leftView.layout(currentView.getLeft() - 320, leftView.getTop(),
currentView.getLeft(), leftView.getBottom());
// place this view just right of the currentView
rightView.layout(currentView.getRight(), rightView.getTop(),
currentView.getRight() + 320, rightView.getBottom());
Which ever of the bottom two lines I put last that direction will work correctly but the other will not.
Here is how I set the leftView and rightView:
final View currentView = myFlipper.getCurrentView();
final View leftView, rightView;
if (currentView == meView) {
Log.d("current layout: ", "me");
leftView = youView;
rightView = youView;
} else if (currentView == youView) {
Log.d("current layout: ", "you");
leftView = meView;
rightView = meView;
} else {
leftView = null;
rightView = null;
}
Is it going to be possible to set it up so that the same view is shown on both sides of the current view?
Thanks stealthcopter
That worked here is the new code if anyone is interested.
if (touchEvent.getX() < oldTouchValue){
// place this view just right of the currentView
rightView.layout(currentView.getRight(), rightView.getTop(),
currentView.getRight() + 320, rightView.getBottom());
}else if (touchEvent.getX() > oldTouchValue) {
// place this view just left of the currentView
leftView.layout(currentView.getLeft() - 320, leftView.getTop(),
currentView.getLeft(), leftView.getBottom());
}
I also moved the setVisibility() calls to the MotionEvent.ACTION_DOWN in an attempt to get rid of some flickering of the views. This helped but I still get a bit.
I have possibly not very constructive suggestion, but if you want it to behave like a home screen, why you don't want to look at the src of that, and modify it to your needs ?
To get rid of the flickering, set setVisibility(View.INVISIBLE) in MotionEvent.ACTION_DOWN. The default value is "GONE" on a view. That means that you can not set Layout() on a view until it's either "VISIBLE" or "INVISIBLE". So in three steps:
Set Visibility to INVISIBLE on the View
Set Layout() on the View
Set Visibility to Visible on the View
If I understand your request, this effect should now be implemented using a View Pager
Looks like this:

Categories

Resources