I am trying to create a custom View that would replace/inflate a certain layout which contains multiple linearlayout predefined and adding those views into each and every predefined layouts.
My predefined layout contains 4 linearlayouts inside relativelayout as parent and inflating this xml in class where it extends Relativelayout and initialising views as below.
private void initView(Context context) {
//Inflate and attach your child XML
View.inflate(context, R.layout.layout_empty_views, this);
llTop = (LinearLayout) findViewById(R.id.linearLayout);
llLeft = (LinearLayout) findViewById(R.id.linearLayout2);
llBottom = (LinearLayout) findViewById(R.id.linearLayout3);
llRight = (LinearLayout) findViewById(R.id.linearLayout4);
}
Now ill accept 12 child views and need to add 3 child to each linearlayout is where im stuck now. In addView function not able to split the the views due to index is always -1.
#Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (llTop == null) {
super.addView(child, index, params);
} else {
//Forward these calls to the content view
llTop.addView(child, index, params);
}
}
In onLayout function, getChildCount is raised more than 12 due to inflating my predefined layout.
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//super.onLayout(changed, l, t, r, b);
// TODO Auto-generated method stub
Log.v("Count::", String.valueOf(getChildCount()));
/*for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
removeViewAt(i);
//llTop.addView(v);
if (i < 3) {
llTop.addView(v);
} else if (i > 2 && i < 6) {
llRight.addView(v);
} else if (i > 5 && i < 9) {
llBottom.addView(v);
} else if (i > 9) {
llLeft.addView(v);
}
}*/
}
When an index of -1 is received in addView(View child, int index), this means it should be added to the end of the ViewGroup.
I'm not sure I exactly understand your intent with this View, you may not need a custom View here. However, I can tell you that you should not be adding views in your onLayout function. This function is where a View should size and position its children.
Check out the Android Developer's guide on creating custom views.
Related
I have expandable views inside CardView thats parent is NestedScrollView. I'm trying to create smooth scroll to child when expand animation ended. But I found only one solution:
scrollView.requestChildFocus(someView, someView);
This code works fine, but, when call requestChildFocus it scrolls immediately, and that annoying me a little bit. Is it possible to scroll to child smoothly?
The childView, to which I wanted to scroll, has CardView parrent, so childView.getTop() returns the value relative to the CardView not to the ScrollView. So, to get top relative to ScrollView I should get childView.getParent().getParent() then cast it to View and call getTop().
Scroll position calculates like
int scrollTo = ((View) childView.getParent().getParent()).getTop() + childView.getTop();
nestedScrollView.smoothScrollTo(0, scrollTo);
more to the answer from #jerry-sha
fun NestedScrollView.smoothScrollTo(view: View) {
var distance = view.top
var viewParent = view.parent
//traverses 10 times
for (i in 0..9) {
if ((viewParent as View) === this) break
distance += (viewParent as View).top
viewParent = viewParent.getParent()
}
smoothScrollTo(0, distance)
}
I had child views at different levels to the scrollview so made this function based off the accepted answer to calculate the distance and scroll
private int findDistanceToScroll(View view){
int distance = view.getTop();
ViewParent viewParent = view.getParent();
//traverses 10 times
for(int i = 0; i < 10; i++) {
if (((View) viewParent).getId() == R.id.journal_scrollview) {
return distance;
}
distance += ((View) viewParent).getTop();
viewParent = viewParent.getParent();
}
Timber.w("view not found");
return 0;
}
Then scroll using
journal_scrollview.smoothScrollTo(0, distance);
You can use my library ViewPropertyObjectAnimator for that.
Assuming mNestedScrollView is your NestedScrollView and mChildView is the child View you want to scroll to, you can do the following:
ViewPropertyObjectAnimator.animate(mNestedScrollView).scrollY(mChildView.getTop()).start();
Just make sure mChildView.getTop() is not 0 at the moment of calling .animate(...).
Edit:
As I said: make sure your View's top is non-zero when CALL .animate(...). In other words: call .animate(...) only when your child View already has dimensions. How can you determine that? For example like this:
mChildView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int width = mChildView.getWidth();
int height = mChildView.getHeight();
if (width > 0 && height > 0) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
mChildView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
mChildView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
ViewPropertyObjectAnimator.animate(mNestedScrollView)
.scrollY(mChildView.getTop())
.start();
}
}
});
Try to read the source code.
svMain.setSmoothScrollingEnabled(true);
Rect rect = new Rect();
rect.top = 0;
rect.left = 0;
rect.right = tv4.getWidth();
rect.bottom =tv4.getHeight();
svMain.requestChildRectangleOnScreen(tv4,rect,false);
rect is the place u want the view to be shown on screen.
The main issue is to calculate the correct x/y position relative to the nestedscrollview. However, most of the answers here try to propose a solution by navigating in the hierarchy and hardcoding the hierarchy-level of the desired view. Imho, this is very error prone, if you change your viewgroup hierarchy.
Therefore, I would suggest to use a much more cleaner approach, which computes the relative position based on a Rect. Then, you can use the nestedscrollview's smoothScrollTo(..) methods to scroll to the desired position.
This should get the work done, where childView is the view you want to scroll to
public static void scrollToView(final NestedScrollView nestedScrollView, final View viewToScrollTo) {
final int[] xYPos = new int[2];
viewToScrollTo.getLocationOnScreen(xYPos);
final int[] scrollxYPos = new int[2];
nestedScrollView.getLocationOnScreen(scrollxYPos);
int yPosition = xYPos[1];
if (yPosition < 0) {
yPosition = 0;
}
nestedScrollView.scrollTo(0, scrollxYPos[1] - yPosition);
}
I find the accepted answer to work, but it is specific to their view structure as of now and it could not be used as a static method to use for all similar scrolls with different view structures. I made a variety of it in a utility class that measures until it find it's ScrollView or NestedScrollView parent. I made it so that the scrollToView(View,View) method should work with both ScrollView and NestedScrollView in case I would update which I use later on or whatever. You could of course call the right "child method" directly.
public static void scrollToView(View scrollView, View scrollToView) {
if (scrollToView == null) {
Log.d(TAG, "scrollToView() failed due to scrollToView == NULL!");
return;
}
if (scrollView instanceof NestedScrollView) {
scrollToInNestedView((NestedScrollView) scrollView, scrollToView);
} else if (scrollView instanceof ScrollView) {
scrollToInScrollView((ScrollView) scrollView, scrollToView);
} else {
Log.d(TAG, "scrollToView() failed due to scrollView not appearing to be any kind of scroll view!");
}
}
public static void scrollToInNestedView(NestedScrollView scrollView, View scrollToView) {
if (scrollView == null || scrollToView == null) {
return;
}
scrollView.post(() -> {
int startY = scrollView.getScrollY();
int requiredY = scrollToView.getTop();
View parent = (View) scrollToView.getParent();
while (parent != null && !(parent instanceof NestedScrollView)) {
requiredY += parent.getTop();
parent = (View) parent.getParent();
}
if (requiredY != startY) {
scrollView.smoothScrollTo(0, requiredY);
}
});
}
public static void scrollToInScrollView(ScrollView scrollView, View scrollToView) {
if (scrollView == null || scrollToView == null) {
return;
}
scrollView.post(() -> {
int startY = scrollView.getScrollY();
int requiredY = scrollToView.getTop();
View parent = (View) scrollToView.getParent();
while (parent != null && !(parent instanceof ScrollView)) {
requiredY += parent.getTop();
parent = (View) parent.getParent();
}
if (requiredY != startY) {
scrollView.smoothScrollTo(0, requiredY);
}
});
}
I'm building a custom view, sort of like a custom bar chart. I'm extending the LinearLayout for this one. The custom view then populates the views from data. Problem is, whenever I want the views to be 'refreshed', I am calling removeAllViews() and similar methods, so the custom view layout is in clean slate, then to repopulate the data, I call addView(), but child views don't show up. Reason why I need to call removeAllViews is for the child views to not duplicate in the custom views.
These are some of the snippets from my custom view, I also implemented onLayout() so whenever I display the custom views I get proper heights for layouting purposes. BarChartData is just a model class for the data that should be displayed in this custom view:
public void setChartData(BarChartData data) {
this.chartData = data;
addBarDataToUi();
}
void addBarDataToUi() {
Log.d(TAG, "Add bar data to UI called");
if (chartData != null) {
//this.removeAllViews(); -> first one I tried, no luck, not displaying views after `addView`
//this.removeAllViewsInLayout(); -> tried this too but no luck
this.removeViewsInLayout(0, this.getChildCount()); // again, to no avail :(
for (int i = 0, count = chartData.getItemCount(); i < count; i++) {
addBarItemDataUi(chartData.getItemByPos(i));
}
Log.d(TAG, "Child count: " + this.getChildCount());
}
}
void addBarItemDataUi(BarItemData data) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.bar_chart_item, this, false);
FrameLayout mainLayout = (FrameLayout) layout.findViewById(R.id.bar_chart_item_main_layout);
//TextView topText = new TextView(getContext());
TextView topText = (TextView) layout.findViewById(R.id.bar_chart_item_top_text);
TextView bottomText = (TextView) layout.findViewById(R.id.bar_chart_item_bottom_text);
topText.setText(String.valueOf(data.percentage));
bottomText.setText(data.title);
mainLayout.setBackgroundColor(data.backgroundColor);
Log.d(TAG, "Height: " + this.getMeasuredHeight() + ", Top text height: " + topText.getMeasuredHeight());
int heightRel = (int) (data.getPercentageFractal() * (double) this.getMeasuredHeight());
mainLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, heightRel));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f);
params.gravity = Gravity.BOTTOM;
layout.setLayoutParams(params);
this.addView(layout);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
Log.d(TAG, "On layout..");
if (chartData != null) {
addBarDataToUi();
}
}
Well, I have searched this problem, there are very few that came up, almost the same scenario and problem, but I think they have not resolved their problems about addView after removeAllViews.
I'm guessing that by calling the removeAllViews() function inside the addBarDataToUi() which is inside onLayout() when the function gets called by setChartData(BarChartData data) it adds the child views which triggers the onLayout() function which calls addBarDataToUi() and removes view and such in some kind of constant loop. The android documentation says to
avoid using removeAllViews() inside onDraw() or any related function
http://developer.android.com/reference/android/view/ViewGroup.html#removeAllViews()
Which I'm assuming might also include the onLayout() function as well.
My best suggestion is moving your removeAllViews() function call to inside your setChartData(BarChartData data) function just before you call addBarDataToUi()
Hope it helps
I want to disable all the field inside the scroll view shown in the picture. I tried using the code below but the code only disables the direct child of linear layout and doesn't disable child for the nested linear layout. How can I disable all child including the children of nested layouts?
LinearLayout myLayout
= (LinearLayout)v.findViewById(R.id.addEditSection1);
for (int i = 0; i < myLayout.getChildCount(); i++) {
View view = myLayout.getChildAt(i);
view.setEnabled(false);
}
Try this recursive function:
public void disableAllViews(View v){
v.setEnabled(false);
if(v instanceof ViewGroup){
for (int i = 0; i < ((ViewGroup)v).getChildCount(); i++) {
View view = ((ViewGroup)v).getChildAt(i);
disableAllViews(view);
}
}
}
And call it as
LinearLayout myLayout
= (LinearLayout)v.findViewById(R.id.addEditSection1);
disableAllViews(myLayout);
Quiet trivial: You got to check recursive.
E.g.:
protected void disableViewElements(ViewGroup container) {
for (int i = 0; i < container.getChildCount(); i++) {
if(container.getChildAt(i) instanceof ViewGroup ) {
disableViewElements((ViewGroup) container.getChildAt(i));
}
else {
View view = container.getChildAt(i);
view.setEnabled(false);
}
}
}
This is really easy with droidQuery:
$.with(myLayout).selectAll().disable();
I am using this in my project
public void setAllViewsEnabled(View view, boolean enabled) {
if (view instanceof ViewGroup)
for (int i = 0; i < ((ViewGroup)view).getChildCount(); i++)
setAllViewsEnabled(((ViewGroup)view).getChildAt(i), enabled);
view.setEnabled(enabled);
}
You can check to execute the last line weather the current view is a TextBox. You can also decide to enable it after using this function.
Remember that if you disable a nested ViewGroup vg1 and then you disable the containing ViewGroup vg0, re-enabling vg0 will result in enabing also vg1. To prevent this you have to save a boolean for each ViewGroup and to change the method.
Inside a RelativeLayout; I have a TableLayout in a ScrollView, and a horizontal LinearLayout. My view is working fine: as the table scrolls in the background, the LinearLayout is static in the foreground. What I now need, is to know exactly where the table and the LinearLayout are intersecting during scrolling.
Since the LinearLayout is static, I know where it is at all time, say y=50pixel. Supposing I have TextViews inside the TableLayout. So as the table scrolls, I want to know which TextView (i.e. tableRow) is intersecting/hidden by the LinearLayout.
Have you tried using View.getLocationOnScreen() on the table rows?
I have a horizontal line at y-offset = 310 pixel. As I scroll my
tableLayout, which is inside a ScrollView, I want to know which row of
the table is intersecting my horizontal line.
You could use the View.getLocationInWindow() method. Initially you'd need to see which row intersects the line and then using a custom ScrollView, follow the scrolling:
// in the onCreate:
scrollView.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int[] loc = new int[2];
ll.getLocationInWindow(loc);
scrollView.setYIntersection(loc[1]);
ViewGroup table = (ViewGroup) scrollView.getChildAt(0);
// find out which row initially intersects our line so
// we can initialize mCrossedRow and position
for (int i = 0; i < table.getChildCount(); i++) {
final View row = table.getChildAt(i);
row.getLocationInWindow(loc);
if (loc[1] <= scrollView.getYIntersection()
&& loc[1] + row.getHeight() >= scrollView
.getYIntersection()) {
scrollView.setCurrIntersection(row, i);
row.setBackgroundColor(Color.RED);
break;
}
}
scrollView.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
}
});
With the custom ScrollView being:
public class CustomScrollView extends ScrollView {
/**
* This will be the current intersected row.
*/
private View mCrossedRow;
/**
* The row position of the intersected row, mCrossedRow.
*/
private int mRowOrder;
/**
* The y value of the intersecting line.
*/
private int mLine;
/**
* Used as a temporary holder for the location retrieval.
*/
private int[] mLocHelper = new int[2];
public CustomScrollView(Context context) {
super(context);
}
public void setYIntersection(int y) {
mLine = y;
}
public int getYIntersection() {
return mLine;
}
public void setCurrIntersection(View row, int childPosition) {
mCrossedRow = row;
mRowOrder = childPosition;
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
// this will be called every time the user scrolls so we need to
// keep updating the position and see if the crossed row still
// intersects the line otherwise move it to the next row.
mCrossedRow.getLocationInWindow(mLocHelper);
if (mLocHelper[1] <= mLine
&& mLocHelper[1] + mCrossedRow.getHeight() >= mLine) {
// do nothing, we're still in the same row
} else {
if (t - oldt > 0) {
// going down so increase the row position
mRowOrder++;
} else {
// going up so decrease the row position
mRowOrder--;
}
// visual effect, the intersecting row will have a red
// background and all other rows will have a white background.
// You could setup a listener here to get notified that a new
// row intersects the line.
mCrossedRow.setBackgroundColor(Color.WHITE);
mCrossedRow = ((ViewGroup) getChildAt(0)).getChildAt(mRowOrder);
mCrossedRow.setBackgroundColor(Color.RED);
}
}
}
I have a problem with my custom ViewGroup. I layout 3 of the children in a row and use a Scroller to scroll to the middle child. Based on the touch input of the user I change the children that should be displayed and request a new layout. Then I layout the children in a new order. But when one child has been displayed in a previous layout run the children lie on top of each other. I checked that the children get layout in the right way and I think the old layout is not deleted and the new children get just drawn on top of the old layout. Is there a way to ensure that the old layout gets cleared or something?
Here is my code:
#Override
public boolean onTouchEvent(MotionEvent event) {
...
case MotionEvent.ACTION_UP:
if(old != current)
this.requestLayout();
else
this.scrollTo(getWidth(), 0);
...
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// show current-1, current, current+1
int count = 0;
for(int i = 1; i >= -1; i--) {
// determine index of child
// the mod does a modulo
int index = mod(current-i, getChildCount());
// position in row from left to right
this.getChildAt(index).layout(count*this.getWidth(), 0, (count+1)*this.getWidth(), height);
count++;
}
// scroll to middle view
this.scrollTo(getWidth(), 0);
...
}
Try to call invalidate() at the end of onLayout().