I want to achieve the following abilities:
Select only one child View inside a GridLayout each time by long clicking it.
A click on the GridLayout or any ancestor parent in the visual hierarchy will deselected selected child View if one already selected.
The problem is when when registering a View.OnLongClickListener callback to child View, neither parent GridLayout nor any ancestor registered callbacks (either View.OnClickListener or View.onTouchEvent) called when clicking on them.
How can I get a selected child inside a GridLayout similar to either AdapterView.OnItemSelectedListener or AdapterView.OnItemLongClickListener and solve the above mentioned problem?
What about storing a "selected" view as a global variable, and removing it when its focus changes? By playing with focusable, focusableInTouchMode and onClick listeners, you could have the right results. I'm not sure that's the best solution, but it works.
What you will need:
A global View variable: the GridLayout's child long clicked, as selected.
(optional) A custom parent container as any ViewGroup: it will set the focusable listeners on all its children [*]. In my tests, I used a LinearLayout and a RelativeLayout.
[*] If you don't use the optional parent custom Class, you have to set android:focusable="true" and android:focusableInTouchMode="true" on all children of the parent ViewGroup. And you'll have to set OnClickListener in order to call removeViewSelected() when the parent ViewGroup is clicked.
Adding Click listeners for GridLayout children: which updates the selected view.
Implementing a Focus listener: which removes the selected view if it's losing focus.
It will handle all focus change state on parent and child hierarchy, see the output:
I used the following pattern:
CoordinatorLayout --- simple root group
ParentLayout --- aka "parentlayout"
Button --- simple Button example
GridLayout --- aka "gridlayout"
FloattingActionButton --- simple Button example
Let's preparing the selected View and its update methods in the Activity:
private View selectedView;
...
private void setViewSelected(View view) {
removeViewSelected();
selectedView = view;
if (selectedView != null) {
// change to a selected background for example
selectedView.setBackgroundColor(
ContextCompat.getColor(this, R.color.colorAccent));
}
}
private View getViewSelected() {
if (selectedView != null) {
return selectedView;
}
return null;
}
private void removeViewSelected() {
if (selectedView != null) {
// reset the original background for example
selectedView.setBackgroundResource(R.drawable.white_with_borders);
selectedView = null;
}
// clear and reset the focus on the parent
parentlayout.clearFocus();
parentlayout.requestFocus();
}
On each GridLayout child, add the Click and LongClick listeners to update or remove the selected view. Mine were TextViews added dynamically, but you could easily create a for-loop to retrieve the children:
TextView tv = new TextView(this);
...
gridlayout.addView(tv);
tv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
removeViewSelected();
}
});
tv.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
setViewSelected(view);
return true;
}
});
Set the FocusChange listener on the parent container:
parentlayout.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View view, boolean hasFocus) {
View viewSelected = getViewSelected();
// if the selected view exists and it lost focus
if (viewSelected != null && !viewSelected.hasFocus()) {
// remove it
removeViewSelected();
}
}
});
Then, the optional custom ViewGroup: it's optional because you could set the focusable state by XML and the clickable listener dynamically, but it seems easier to me. I used this following custom Class as parent container:
public class ParentLayout extends RelativeLayout implements View.OnClickListener {
public ParentLayout(Context context) {
super(context);
init();
}
public ParentLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ParentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
// handle focus and click states
public void init() {
setFocusable(true);
setFocusableInTouchMode(true);
setOnClickListener(this);
}
// when positioning all children within this
// layout, add their focusable state
#Override
protected void onLayout(boolean c, int l, int t, int r, int b) {
super.onLayout(c, l, t, r, b);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
child.setFocusable(true);
child.setFocusableInTouchMode(true);
}
// now, even the Button has a focusable state
}
// handle the click events
#Override
public void onClick(View view) {
// clear and set the focus on this viewgroup
this.clearFocus();
this.requestFocus();
// now, the focus listener in Activity will handle
// the focus change state when this layout is clicked
}
}
For example, this is the layout I used:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout ...>
<com.app.ParentLayout
android:id="#+id/parent_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal">
<Button
android:id="#+id/sample_button"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:text="A Simple Button"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"/>
<android.support.v7.widget.GridLayout
android:id="#+id/grid_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_above="#id/sample_button" .../>
</com.app.ParentLayout>
<android.support.design.widget.FloatingActionButton .../>
</android.support.design.widget.CoordinatorLayout>
Hope this will be useful.
Use the following code :
int last_pos = -1;
GridLayout gridLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gridLayout = (GridLayout) findViewById(R.id.gridLayout);
int child_count = gridLayout.getChildCount();
for(int i =0;i<child_count;i++){
gridLayout.getChildAt(i).setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
//Deselect previous
if(last_pos!=-1) gridLayout.getChildAt(last_pos).setSelected(false);
//Select the one you clicked
view.setSelected(true);
last_pos = gridLayout.indexOfChild(view);
return false;
}
});
}
//Remove focus if the parent is clicked
gridLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
gridLayout.getChildAt(last_pos).setSelected(false);
}
});
Related
I have button instead of floating action button i want to anchor it with snack bar as it anchor with floating action bar like floating action button moves up when snack bar is shown, with floating action bar we do it like this:
Snackbar.make(fab, s, Snackbar.LENGTH_SHORT);
how can I do with simple button as snackbar and button don't know snackbar presence and vice versa and moves independently
Thanks
The Docs say this
Having a CoordinatorLayout in your view hierarchy allows Snackbar to enable certain features, such as swipe-to-dismiss and automatically moving of widgets like FloatingActionButton.
So as you long as your button is directly under the Coordinator Layout and you pass the button to the Snackbar like this
Snackbar.make(yourButton, s, Snackbar.LENGTH_SHORT); . The moving of widgets upwards should work.
You have to attach a Behavior to the View you want to move along with Snakebar movement
In here I make a custom View and attach the behavior to it
#CoordinatorLayout.DefaultBehavior(CustomView.Behavior.class)
public class CustomView extends View {
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public static class Behavior extends CoordinatorLayout.Behavior<CustomView> {
public Behavior() {
super();
}
public Behavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onAttachedToLayoutParams(#NonNull CoordinatorLayout.LayoutParams lp) {
if (lp.dodgeInsetEdges == Gravity.NO_GRAVITY) {
// This setting makes it works (learn from FloatingActionButton.Behavior)
lp.dodgeInsetEdges = Gravity.BOTTOM;
}
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, CustomView child,
View dependency) {
return false;
}
}
}
Use this CustomView in your layout and it should work
The FloatingActionButton does the same thing, it has an inner Behavior which do many more things with the shadow padding, show/hide base on AppBarLayout movement. Every movement in CoordinatorLayout base on its Behavior mechanism
Ref here: http://saulmm.github.io/mastering-coordinator
P/S: About Snakebar.make usage, pass the CoordinatorLayout into view parameter, don't pass any other components, that will save the system performance. If you look at the Snakebar code, it has a loop, uses the view parameter to find the root view. The root view is what it wants:
private static ViewGroup findSuitableParent(View view) {
ViewGroup fallback = null;
do {
if (view instanceof CoordinatorLayout) {
// We've found a CoordinatorLayout, use it
return (ViewGroup) view;
} else if (view instanceof FrameLayout) {
if (view.getId() == android.R.id.content) {
// If we've hit the decor content view, then we didn't find a CoL in the
// hierarchy, so use it.
return (ViewGroup) view;
} else {
// It's not the content view but we'll use it as our fallback
fallback = (ViewGroup) view;
}
}
if (view != null) {
// Else, we will loop and crawl up the view hierarchy and try to find a parent
final ViewParent parent = view.getParent();
view = parent instanceof View ? (View) parent : null;
}
} while (view != null);
// If we reach here then we didn't find a CoL or a suitable content view so we'll fallback
return fallback;
}
I have fragment from which I'm launching activity with shared element transition that has viewpager in it, the enter transition works fine but when i scroll in view pager and finish transition the shared image comes from left side which is not desired it should reposition itself to where it was launched, here is my code:
Intent myIntent = new Intent(getActivity(), EnlargeActivity.class);
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(getActivity(),
imageView,
ViewCompat.getTransitionName(imageView));
startActivity(myIntent, options.toBundle());
I'm updating view and its name in activity that contains viewpager when finishing activity, but its going with blink:
public void finishAfterTransition() {
setEnterSharedElementCallback(new SharedElementCallback() {
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
// Clear all current shared views and names
names.clear();
sharedElements.clear();
ViewGroup viewGroup = (ViewGroup) viewPagerDetail.getAdapter()
.instantiateItem(viewPagerDetail, viewPagerDetail.getCurrentItem());
if (viewGroup == null) {
return;
}
// Map the first shared element name to the child ImageView.
sharedElements.put(viewGroup.findViewById(R.id.img).getTransitionName(), viewGroup.findViewById(R.id.img));
// setExitSharedElementCallback((SharedElementCallback) this);
}
});
super.finishAfterTransition();
Basically, Android start the transition with your pre-defined View and transitionName and automatically use the same properties for the return transition. When you change your focused View in ViewPager, Android doesn't know about that and keep the transition on the previous one on its way back. So you need to inform Android about the changes:
Remap the transition properties: Use setEnterSharedElementCallback to change the transitionName and View to the new one before returning from Activity2.
Wait for the Activity1 to finish rendering addOnPreDrawListener.
It's a bit complex in the final implementation. But you can look at my sample code https://github.com/tamhuynhit/PhotoGallery. I try to implement the shared-element-transition from many simple to complex sections.
Your problem appeared from Level 3 and solved in Level 4.
I am writing a tutorial about this but it's not in English so hope the code can help
UPDATE 1: Work flow
Here is how I implement it in my code:
Override finishAfterTransition in Activity2 and call setEnterSharedElementCallback method to re-map the current selected item in ViewPager. Also, call setResult to pass the new selected index back to previous activity here.
#Override
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void finishAfterTransition() {
setEnterSharedElementCallback(new SharedElementCallback() {
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
View selectedView = getSelectedView();
if (selectedView == null)
return;
// Clear all current shared views and names
names.clear();
sharedElements.clear();
// Store new selected view and name
String transitionName = ViewCompat.getTransitionName(selectedView);
names.add(transitionName);
sharedElements.put(transitionName, selectedView);
setExitSharedElementCallback((SharedElementCallback) null);
}
});
Intent intent = new Intent();
intent.putExtra(PHOTO_FOCUSED_INDEX, mCurrentIndex);
setResult(RESULT_PHOTO_CLOSED, intent);
super.finishAfterTransition();
}
Write a custom ShareElementCallback so I can set the callback before knowing which View is going to be used.
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static class CustomSharedElementCallback extends SharedElementCallback {
private View mView;
/**
* Set the transtion View to the callback, this should be called before starting the transition so the View is not null
*/
public void setView(View view) {
mView = view;
}
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
// Clear all current shared views and names
names.clear();
sharedElements.clear();
// Store new selected view and name
String transitionName = ViewCompat.getTransitionName(mView);
names.add(transitionName);
sharedElements.put(transitionName, mView);
}
}
Override onActivityReenter in Activity1, get the selected index from the result Intent. Set setExitSharedElementCallback to re-map new selected View when the transition begins.Call supportPostponeEnterTransition to delay a bit because your new View may not be rendered at this point. Use getViewTreeObserver().addOnPreDrawListener to listen for the layout changes, find the right View by the selected index and continue the transition supportStartPostponedEnterTransition.
#Override
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onActivityReenter(int resultCode, Intent data) {
if (resultCode != LevelFourFullPhotoActivity.RESULT_PHOTO_CLOSED || data == null)
return;
final int selectedIndex = data.getIntExtra(LevelFourFullPhotoActivity.PHOTO_FOCUSED_INDEX, -1);
if (selectedIndex == -1)
return;
// Scroll to the new selected view in case it's not currently visible on the screen
mPhotoList.scrollToPosition(selectedIndex);
final CustomSharedElementCallback callback = new CustomSharedElementCallback();
getActivity().setExitSharedElementCallback(callback);
// Listen for the transition end and clear all registered callback
getActivity().getWindow().getSharedElementExitTransition().addListener(new Transition.TransitionListener() {
#Override
public void onTransitionStart(Transition transition) {}
#Override
public void onTransitionPause(Transition transition) {}
#Override
public void onTransitionResume(Transition transition) {}
#Override
public void onTransitionEnd(Transition transition) {
removeCallback();
}
#Override
public void onTransitionCancel(Transition transition) {
removeCallback();
}
private void removeCallback() {
if (getActivity() != null) {
getActivity().getWindow().getSharedElementExitTransition().removeListener(this);
getActivity().setExitSharedElementCallback((SharedElementCallback) null);
}
}
});
// Pause transition until the selected view is fully drawn
getActivity().supportPostponeEnterTransition();
// Listen for the RecyclerView pre draw to make sure the selected view is visible,
// and findViewHolderForAdapterPosition will return a non null ViewHolder
mPhotoList.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
mPhotoList.getViewTreeObserver().removeOnPreDrawListener(this);
RecyclerView.ViewHolder holder = mPhotoList.findViewHolderForAdapterPosition(selectedIndex);
if (holder instanceof ViewHolder) {
callback.setView(((ViewHolder) holder).mPhotoImg);
}
// Continue the transition
getActivity().supportStartPostponedEnterTransition();
return true;
}
});
}
UPDATE 2: getSelectedItem
To get selected View from the ViewPager, don't use getChildAt or you get the wrong View, use findViewWithTag instead
In the PagerAdapter.instantiateItem, use position as tag for each View:
#Override
public View instantiateItem(ViewGroup container, int position) {
// Create the View
view.setTag(position)
// ...
}
Listen to onPageSelected event to get the selected index:
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
mSelectedIndex = position;
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Call getSelectedView to get the current view by the selected index
private View getSelectedView() {
try {
return mPhotoViewPager.findViewWithTag(mSelectedIndex);
} catch (IndexOutOfBoundsException | NullPointerException ex) {
return null;
}
}
This is actually a default behavior, I was struggling SharedElementTransitions a lot, but I have nested fragments. I got my solution from an article (very recent article), it shows an implementation with a RecyclerView, which I assume you have. In short, the solution is to override onLayoutChange :
recyclerView.addOnLayoutChangeListener(
new OnLayoutChangeListener() {
#Override
public void onLayoutChange(View view,
int left,
int top,
int right,
int bottom,
int oldLeft,
int oldTop,
int oldRight,
int oldBottom) {
recyclerView.removeOnLayoutChangeListener(this);
final RecyclerView.LayoutManager layoutManager =
recyclerView.getLayoutManager();
View viewAtPosition =
layoutManager.findViewByPosition(MainActivity.currentPosition);
// Scroll to position if the view for the current position is null (not
// currently part of layout manager children), or it's not completely
// visible.
if (viewAtPosition == null
|| layoutManager.isViewPartiallyVisible(viewAtPosition, false, true)){
recyclerView.post(()
-> layoutManager.scrollToPosition(MainActivity.currentPosition));
}
}
});
Here is the article, and you will also find the project on GitHub.
I have a subclass called AuditQuestionEntry that extends LinearLayout. In addition to other fields, this layout contains a button. On my main activity ("AuditActivity") I have multiple instances of AuditQuestionEntry. From AuditActivity, I am setting an OnClickListener for the button in each of the AuditQuestionEntry layouts. In the OnClick method I need to access a parameter that's in the AuditQuestionEntry associated with the button that was clicked.
In the OnClickListener I call getParent() and I can cast that to AuditQuestionEntry. However, when I try to access a parameter from there it returns null (or 0 in my case because it's an int). Basically, it appears that getParent() is returning a new instance of the class and not the actual instance that contains the button.
Layout - audit_question_entry.xml:
<TextView
android:id="#+id/auditQuestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Audit Question..."
android:textSize="20dp" />
<Button
android:id="#+id/btnTakeAuditPhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Photo" >
</Button>
Here's the AuditQuestionEntry.java:
public class AuditQuestionEntry extends LinearLayout {
// Define controls
private TextView auditQuestion;
private RatingBar auditRating;
private EditText auditComment;
private ImageView imageView;
private Button photoButton;
private static final int CAMERA_REQUEST = 1888;
private static final int REQUEST_IMAGE_CAPTURE = 1;
public int AuditQuestionNumber; // NEED TO GET THIS!
private Context _ctx;
public ClosetAuditQuestionObj AuditQuestion;
private String PhotoFolder;
private String FullPhotoPathAndName;
public AuditQuestionEntry(Context context) {
super(context);
// InflateView();
throw new RuntimeException("Valid AuditQuestionNumber must be passed to this class via the XML parameters: workbench:AuditQuestionNumber.");
}
public AuditQuestionEntry(Context context, AttributeSet attrs) {
super(context, attrs);
this._ctx = context;
initAttributes(attrs);
}
// Used to grab the AuditQuestionNumber attribute from the XML declaration
// for this layout
private void initAttributes(AttributeSet attrs) {
TypedArray a = _ctx.obtainStyledAttributes(attrs, R.styleable.AuditQuestionEntry);
AuditQuestionNumber = attrs.getAttributeIntValue("http://schemas.android.com/apk/com.bc.workbench", "AuditQuestionNumber", 0);
a.recycle();
}
Inside AuditActivity onCreate() I have the following:
OnClickListener btnPhotoListener = new OnClickListener() {
#Override
public void onClick(final View v) {
View viewParent = (View) v.getParent();
AuditQuestionEntry clickedAudit = (AuditQuestionEntry) viewParent;
// On the following line, clickedAudit.AuditQuestionNumber is always 0 even though it was initialized and
// displays properly on screen.
Toast.makeText(CurrentContext, "Audit Question #" + clickedAudit.AuditQuestionNumber, Toast.LENGTH_SHORT).show();
}
};
// Set event listener for photo buttons
// =============================================
((Button) AuditQuestion1.findViewById(R.id.btnTakeAuditPhoto)).setOnClickListener(btnPhotoListener);
((Button) AuditQuestion2.findViewById(R.id.btnTakeAuditPhoto)).setOnClickListener(btnPhotoListener);
Where in the XML are you setting the attribute for the AuditQuestionNumber? The constructor is defaulting the value of the class attribute to 0 if the XML being used to inflate the class doesn't include the attribute:
AuditQuestionNumber = attrs.getAttributeIntValue("http://schemas.android.com/apk/com.bc.workbench", "AuditQuestionNumber", 0);
So I had my listview working perfectly then I decided to add a context menu. As soon as I did that whenever I normal clicked an item in my listview, the entire list gets inverted on the first click. Subsequent clicks do nothing to the order, but when the first item is de-selected again the list returns to normal. When I take out the context menu logic that I added, the list view problem does not go away.
I've attached a debugger and the elements in my list adapter are never reordered, and the ListView itself is never set to reverse with .setStackFromBottom()
Here is my onClick listener registered to handle the click events of the list view items:
public void onClick(View v) {
ViewHolder holder = (ViewHolder) v.getTag();
CheckBox b = holder.box;
Boolean check = b.isChecked();
b.setChecked(!check);
if (!check) {
mChecked.add(holder.fu);
if (mChecked.size() == 1) {
buttonLayout.setVisibility(View.VISIBLE);
}
} else {
mChecked.remove(holder.fu);
if (mChecked.size() == 0) {
buttonLayout.setVisibility(View.GONE);
}
}
}
The viewholder class just holds references to objects I use in the listview for optimizations. I cannot figure out why this is causing my list to invert when displayed, I've tried moving the listener to a different view in the layout, I've tried re-writing the listener, nothing seems to work! Any advice would be appreciated.
Edit: here is the code for the view holder
/** Class to provide a holder for ListViews. Used for optimization */
private class ViewHolder {
TextView date;
TextView gallons;
TextView cost;
TextView cpg;
TextView mpg;
CheckBox box;
FillUp fu;
}
as well as the adapter:
public class FillUpAdapter extends BaseAdapter {
ArrayList<FillUp> mElements;
ArrayList<FillUp> mChecked;
Context mContext;
public FillUpAdapter(Context c, ArrayList<FillUp> data) {
mContext = c;
mElements = data;
mChecked = new ArrayList<FillUp>();
}
public void clearChecked() {
mChecked.clear();
}
public ArrayList<FillUp> getChecked() {
return mChecked;
}
public boolean remove(FillUp f) {
mChecked.remove(f);
return mElements.remove(f);
}
#Override
public int getCount() {
return mElements.size();
}
#Override
public Object getItem(int arg0) {
return mElements.get(arg0);
}
#Override
public long getItemId(int arg0) {
return mElements.get(arg0).getId();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout layout;
ViewHolder holder;
if (convertView != null) {
layout = (LinearLayout) convertView;
holder = (ViewHolder) layout.getTag();
} else {
layout = (LinearLayout) LayoutInflater.from(mContext).inflate(
R.layout.fillup_list_item, parent, false);
holder = new ViewHolder();
holder.cost = (TextView) layout
.findViewById(R.id.fillUpListTotalValue);
holder.cpg = (TextView) layout
.findViewById(R.id.fillUpListCostPerGal);
holder.gallons = (TextView) layout
.findViewById(R.id.fillUpListGalValue);
holder.mpg = (TextView) layout
.findViewById(R.id.fillUpMPGText);
holder.date = (TextView) layout
.findViewById(R.id.fillUpListDate);
holder.box = (CheckBox) layout
.findViewById(R.id.fillUpListCheckBox);
holder.fu = (FillUp) getItem(position);
layout.setTag(holder);
}
holder.date.setText(holder.fu.getDate());
holder.gallons.setText(holder.fu.getGallonsText());
holder.cpg.setText(holder.fu.getCostText());
holder.cost.setText(holder.fu.getTotalText());
holder.mpg.setText(String.format("%03.1f MPG",holder.fu.getMPG()));
if (convertView != null) {
holder.box.setChecked(mChecked.contains(holder.fu));
}
layout.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
ViewHolder holder = (ViewHolder) v.getTag();
CheckBox b = holder.box;
Boolean check = b.isChecked();
b.setChecked(!check);
if (!check) {
mChecked.add(holder.fu);
if (mChecked.size() == 1) {
buttonLayout.setVisibility(View.VISIBLE);
}
} else {
mChecked.remove(holder.fu);
if (mChecked.size() == 0) {
buttonLayout.setVisibility(View.GONE);
}
}
}
});
return layout;
}
}
UPDATE:
Ok, so I've narrowed it down to the visibility change on the buttonLayout view, which is a linear layout of buttons on the bottom of the Activity's layout, underneath the ListView. Whenever I change that view's visibility to View.VISIBLE (which happens when the first item is checked) the list's order is reversed. The order is restored when the view's visibility is set to View.GONE
I have no idea what would cause that though :(
After narrowing the scope a bit more, I discovered the problem was not the changing of the visibility of my button bar, but actually the passing around of FillUp objects in holder.fu of my ViewHolder class. By changing that to instead reference the adapter's getItem(position) method, everything seemed to work out. Quite an odd bug, since the adapter itself was not having the order of the elements changed, but passing around a reference to the object made it very unhappy.
If your listview background color changes when you click on it, I think it is about your theme. Just play with the cache color parameters of your listview, here is an example:
<ListView
android:layout_width="fill_parent"
android:id="#android:id/list"
android:scrollingCache="true"
android:persistentDrawingCache="all"
android:cacheColorHint="#0000"
android:layout_height="wrap_content"
android:fastScrollEnabled="true"
android:stackFromBottom="true"
android:smoothScrollbar="true"
android:paddingTop="115dip">
</ListView>
Is there any way to scroll a ScrollView programmatically to a certain position?
I have created dynamic TableLayout which is placed in a ScrollView. So I want that on a specific action (like clicking a Button, etc.) the particular row should scroll automatically to a top position.
Is it possible?
The answer from Pragna does not work always, try this:
mScrollView.post(new Runnable() {
public void run() {
mScrollView.scrollTo(0, mScrollView.getBottom());
}
});
or
mScrollView.post(new Runnable() {
public void run() {
mScrollView.fullScroll(mScrollView.FOCUS_DOWN);
}
});
if You want to scroll to start
mScrollView.post(new Runnable() {
public void run() {
mScrollView.fullScroll(mScrollView.FOCUS_UP);
}
});
ScrollView sv = (ScrollView)findViewById(R.id.scrl);
sv.scrollTo(0, sv.getBottom());
or
sv.scrollTo(5, 10);
I wanted the scrollView to scroll directly after onCreateView() (not after e.g. a button click). To get it to work I needed to use a ViewTreeObserver:
mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mScrollView.post(new Runnable() {
public void run() {
mScrollView.fullScroll(View.FOCUS_DOWN);
}
});
}
});
But beware that this will be called everytime something gets layouted (e.g if you set a view invisible or similar) so don't forget to remove this listener if you don't need it anymore with:
public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim) on SDK Lvl < 16
or
public void removeOnGlobalLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim) in SDK Lvl >= 16
There are a lot of good answers here, but I only want to add one thing. It sometimes happens that you want to scroll your ScrollView to a specific view of the layout, instead of a full scroll to the top or the bottom.
A simple example: in a registration form, if the user tap the "Signup" button when a edit text of the form is not filled, you want to scroll to that specific edit text to tell the user that he must fill that field.
In that case, you can do something like that:
scrollView.post(new Runnable() {
public void run() {
scrollView.scrollTo(0, editText.getBottom());
}
});
or, if you want a smooth scroll instead of an instant scroll:
scrollView.post(new Runnable() {
public void run() {
scrollView.smoothScrollTo(0, editText.getBottom());
}
});
Obviously you can use any type of view instead of Edit Text. Note that getBottom() returns the coordinates of the view based on its parent layout, so all the views used inside the ScrollView should have only a parent (for example a Linear Layout).
If you have multiple parents inside the child of the ScrollView, the only solution i've found is to call requestChildFocus on the parent view:
editText.getParent().requestChildFocus(editText, editText);
but in this case you cannot have a smooth scroll.
I hope this answer can help someone with the same problem.
Use something like this:
mScrollView.scrollBy(10, 10);
or
mScrollView.scrollTo(10, 10);
Try using scrollTo method More Info
If you want to scroll instantly then you can use :
ScrollView scroll= (ScrollView)findViewById(R.id.scroll);
scroll.scrollTo(0, scroll.getBottom());
OR
scroll.fullScroll(View.FOCUS_DOWN);
OR
scroll.post(new Runnable() {
#Override
public void run() {
scroll.fullScroll(View.FOCUS_DOWN);
}
});
Or if you want to scroll smoothly and slowly so you can use this:
private void sendScroll(){
final Handler handler = new Handler();
new Thread(new Runnable() {
#Override
public void run() {
try {Thread.sleep(100);} catch (InterruptedException e) {}
handler.post(new Runnable() {
#Override
public void run() {
scrollView.fullScroll(View.FOCUS_DOWN);
}
});
}
}).start();
}
**to scroll up to desired height. I have come up with some good solution **
scrollView.postDelayed(new Runnable() {
#Override
public void run() {
scrollView.scrollBy(0, childView.getHeight());
}
}, 100);
Yes, you can.
Let's say you got one Layout and inside that, you got many Views. So if you want to scroll to any View programmatically, you have to write the following code snippet:
For example:
content_main.xml
<ScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="#+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/txtView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
MainActivity.java
ScrollView scrollView = (ScrollView) findViewById(R.id.scrollView);
Button btn = (Button) findViewById(R.id.ivEventBanner);
TextView txtView = (TextView) findViewById(R.id.ivEditBannerImage);
If you want to scroll to a specific View, let's say txtview, in this case, just write:
scrollView.smoothScrollTo(txtView.getScrollX(),txtView.getScrollY());
And you are done.
I got this to work to scroll to the bottom of a ScrollView (with a TextView inside):
(I put this on a method that updates the TextView)
final ScrollView myScrollView = (ScrollView) findViewById(R.id.myScroller);
myScrollView.post(new Runnable() {
public void run() {
myScrollView.fullScroll(View.FOCUS_DOWN);
}
});
Note: if you already in a thread, you have to make a new post thread, or it's not scroll new long height till the full end (for me).
For ex:
void LogMe(final String s){
runOnUiThread(new Runnable() {
public void run() {
connectionLog.setText(connectionLog.getText() + "\n" + s);
final ScrollView sv = (ScrollView)connectLayout.findViewById(R.id.scrollView);
sv.post(new Runnable() {
public void run() {
sv.fullScroll(sv.FOCUS_DOWN);
/*
sv.scrollTo(0,sv.getBottom());
sv.scrollBy(0,sv.getHeight());*/
}
});
}
});
}
Adding another answer that does not involve coordinates.
This will bring your desired view to focus (but not to the top position) :
yourView.getParent().requestChildFocus(yourView,yourView);
public void RequestChildFocus (View child, View focused)
child - The child of this ViewParent that wants focus. This view will contain the focused view. It is not necessarily the view that actually has focus.
focused - The view that is a descendant of child that actually has focus
Everyone is posting such complicated answers.
I found an easy answer, for scrolling to the bottom, nicely:
final ScrollView myScroller = (ScrollView) findViewById(R.id.myScrollerView);
// Scroll views can only have 1 child, so get the first child's bottom,
// which should be the full size of the whole content inside the ScrollView
myScroller.smoothScrollTo( 0, myScroller.getChildAt( 0 ).getBottom() );
And, if necessary, you can put the second line of code, above, into a runnable:
myScroller.post( new Runnable() {
#Override
public void run() {
myScroller.smoothScrollTo( 0, myScroller.getChildAt( 0 ).getBottom() );
}
}
It took me much research and playing around to find this simple solution. I hope it helps you, too! :)
just page scroll:
ScrollView sv = (ScrollView) findViewById(your_scroll_view);
sv.pageScroll(View.FOCUS_DOWN);
I was using the Runnable with sv.fullScroll(View.FOCUS_DOWN);
It works perfectly for the immediate problem, but that method makes ScrollView take the Focus from the entire screen, if you make that AutoScroll to happen every time, no EditText will be able to receive information from the user, my solution was use a different code under the runnable:
sv.scrollTo(0, sv.getBottom() + sv.getScrollY());
making the same without losing focus on important views
greetings.
it's working for me
mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mScrollView.post(new Runnable() {
public void run() {
mScrollView.fullScroll(View.FOCUS_DOWN);
}
});
}
});
private int totalHeight = 0;
ViewTreeObserver ScrollTr = loutMain.getViewTreeObserver();
ScrollTr.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
loutMain.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
loutMain.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
TotalHeight = loutMain.getMeasuredHeight();
}
});
scrollMain.smoothScrollTo(0, totalHeight);
I had to create Interface
public interface ScrollViewListener {
void onScrollChanged(ScrollViewExt scrollView,
int x, int y, int oldx, int oldy);
}
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
public class CustomScrollView extends ScrollView {
private ScrollViewListener scrollViewListener = null;
public ScrollViewExt(Context context) {
super(context);
}
public CustomScrollView (Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public CustomScrollView (Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (scrollViewListener != null) {
scrollViewListener.onScrollChanged(this, l, t, oldl, oldt);
}
}
}
<"Your Package name ".CustomScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true"
android:scrollbars="vertical">
private CustomScrollView scrollView;
scrollView = (CustomScrollView)mView.findViewById(R.id.scrollView);
scrollView.setScrollViewListener(this);
#Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {
// We take the last son in the scrollview
View view = (View) scrollView.getChildAt(scrollView.getChildCount() - 1);
int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));
// if diff is zero, then the bottom has been reached
if (diff == 0) {
// do stuff
//TODO keshav gers
pausePlayer();
videoFullScreenPlayer.setVisibility(View.GONE);
}
}