Android: custom views get drawn at wrong x,y coordinates - android

I'm trying to create a custom view, inherit from view group, and layout custom sub-views inside this view group in a customized way. Basically I'm trying to create a calendar view similar to the one in outlook, where each event takes up screen height relative to its length.
I initialize an ArrayList of View in the ViewGroup's constructor, override onMeasure, onLayout and onDraw, and everything works well, except... the rendered views all render starting at (0,0), even though I set their left and right properties to other values. Their width and height come out ok, only their top and left are wrong.
This is the code, which I abbreviated for clarity and simplicity:
public class CalendarDayViewGroup extends ViewGroup {
private Context mContext;
private int mScreenWidth = 0;
private ArrayList<Event> mEvents;
private ArrayList<View> mEventViews;
// CalendarGridPainter is a class that draws the background grid.
// this one works fine so I didn't write its actual code here.
// it just takes a Canvas and draws lines on it.
// I also tried commenting out this class and got the same result,
// so this is DEFINITELY not the problem.
private CalendarGridPainter mCalendarGridPainter;
public CalendarDayViewGroup(Context context, Date date) {
super(context);
init(date, context);
}
//... other viewGroup constructors go here...
private void init(Date date, Context context) {
mContext = context;
// the following line loads events from a database
mEvents = AppointmentsRepository.getByDateRange(date, date);
// inflate all event views
mEventViews = new ArrayList<>();
LayoutInflater inflater = LayoutInflater.from(mContext);
for (int i = 0; i < mEvents.size(); i++) {
View view = getSingleEventView(mEvents.get(i), inflater);
mEventViews.add(view);
}
// set this flag so that the onDraw event is called
this.setWillNotDraw(false);
}
private View getSingleEventView(Event event, LayoutInflater inflater) {
View view = inflater.inflate(R.layout.single_event_view, null);
// [set some properties in the view's sub-views]
return view;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
// get screen width and create a new GridPainter if needed
int screenWidth = MeasureSpec.getSize(widthMeasureSpec);
if (mScreenWidth != screenWidth)
{
mScreenWidth = screenWidth;
mCalendarGridPainter = new CalendarGridPainter(screenWidth);
}
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
// event width is the same as screen width
int specWidth = MeasureSpec.makeMeasureSpec(mScreenWidth, MeasureSpec.EXACTLY);
// event height is calculated by its length, the calculation was ommited here for simplicity
int eventHeight = 350; // actual calculation goes here...
int specHeight = MeasureSpec.makeMeasureSpec(eventHeight, MeasureSpec.EXACTLY);
child.measure(specWidth, specHeight);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
int eventLeft = 0;
int eventTop = (i + 1) * 200; // test code, make each event start 200 pixels after the previous one
int eventWidth = eventLeft + child.getMeasuredWidth();
int eventHeight = eventTop + child.getMeasuredHeight();
child.layout(eventLeft, eventTop, eventWidth, eventHeight);
}
}
#Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
for (View view : mEventViews) {
view.draw(canvas);
}
super.onDraw(canvas);
}
}

For some reason, it seems like the way children are drawn with ViewGroups is that the ViewGroup translates the canvas to child's position then draws the child at 0,0.
But as it turns out, ViewGroup will handle all the drawing of children for you. I think if you simplify your onDraw() method you should be all set:
#Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
super.onDraw(canvas);
}
Now that I'm looking at your code further, I noticed you are inflating your child views within the code for your ViewGroup. It would be best to do all that outside your ViewGroup, add those views using addView(), then use getChildCount() and getChildAt() to access the child views during onLayout().

Related

Custom View won't show with onDraw

I have built a custom view which extends LinearLayout. The view creates in its constructor several other views with a background of a shape I've created like this:
private void addBars()
{
setOrientation(LinearLayout.HORIZONTAL);
for(int i = 0; i < numberOfBars; i++)
{
if(bars[i] != null)
{
removeView(bars[i]);
}
View view = new View(getContext());
view.setBackgroundResource(R.drawable.rounded_corner_bars);
if(i < onBarIndex)
{
setBarColor(view, offBarColorBelowIndex);
}
else if(i == onBarIndex)
{
setBarColor(view, onBarColor);
}
else
{
setBarColor(view, offBarAfterIndex);
}
LayoutParams params = new LayoutParams((int) barWidth, LOWEST_BAR_HEIGHT + BAR_HEIGHT_DIFFERENCE*i);
params.setMargins(spaceBetweenBarsPixels, 0, spaceBetweenBarsPixels, 0);
params.gravity = Gravity.BOTTOM;
view.setLayoutParams(params);
bars[i] = view;
addView(view);
}
}
The onBarIndex can be changed from the outside and inside like this:
public void setOnBarIndex(int index)
{
onBarIndex = index;
addBars();
}
What I'm trying to do is update the index continuously and thus let the view redraw itself after a certain amount of interval. I'm doing it like this:
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if(recording && recorder != null)
{
int onIndex = (int)recorder.getOnIndex();
setOnBarIndex(onIndex);
postInvalidateDelayed(ANIMATION_INTERVAL);
}
}
The problem I'm facing is that when the onDraw method is present, the view is simply not drawing itself and the place in the layout where the view is supposed to be, seems blank. The onDraw method itself IS being called continuously and the recorder object can yield different results every time. If I comment the onDraw method then the view is showing like its supposed to, but then it won't redraw itself every interval.
Why is that?

How does this "StickyScrollView" implementation stick the view to the top of the ScrollView?

Typically how sticky headers work is that there's some sort of scrollable data that is divided into sections, each with their own header, and as you scroll down, the headers of subsequent sections replace the header at the top of the ScrollView.
What I need is to have additional sticky headers within each respective section. For example, if header1 is stuck to the top, its first section's header --header1a-- is stuck underneath it, but when we get to section 1b, 1b's header will replace 1a's, but leaving header1 stuck in the same place; and when we finally get to section 2, header2 will replace the currently stuck headers from the previous section -- header1 and header1b.
Here is a ScrollView implementation that implements sticky headers in a one-dimensional fashion:
https://github.com/emilsjolander/StickyScrollViewItems
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import java.util.ArrayList;
/**
*
* #author Emil Sj�lander - sjolander.emil#gmail.com
*
*/
public class StickyScrollView extends ScrollView {
/**
* Tag for views that should stick and have constant drawing. e.g. TextViews, ImageViews etc
*/
public static final String STICKY_TAG = "sticky";
/**
* Flag for views that should stick and have non-constant drawing. e.g. Buttons, ProgressBars etc
*/
public static final String FLAG_NONCONSTANT = "-nonconstant";
/**
* Flag for views that have aren't fully opaque
*/
public static final String FLAG_HASTRANSPARANCY = "-hastransparancy";
/**
* Default height of the shadow peeking out below the stuck view.
*/
private static final int DEFAULT_SHADOW_HEIGHT = 10; // dp;
private ArrayList<View> mStickyViews;
private View mCurrentlyStickingView;
private float mStickyViewTopOffset;
private int mStickyViewLeftOffset;
private boolean mRedirectTouchesToStickyView;
private boolean mClippingToPadding;
private boolean mClipToPaddingHasBeenSet;
private int mShadowHeight;
private Drawable mShadowDrawable;
private final Runnable mInvalidateRunnable = new Runnable() {
#Override
public void run() {
if(mCurrentlyStickingView !=null){
int l = getLeftForViewRelativeOnlyChild(mCurrentlyStickingView);
int t = getBottomForViewRelativeOnlyChild(mCurrentlyStickingView);
int r = getRightForViewRelativeOnlyChild(mCurrentlyStickingView);
int b = (int) (getScrollY() + (mCurrentlyStickingView.getHeight() + mStickyViewTopOffset));
invalidate(l,t,r,b);
}
postDelayed(this, 16);
}
};
public StickyScrollView(Context context) {
this(context, null);
}
public StickyScrollView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.scrollViewStyle);
}
public StickyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.StickyScrollView, defStyle, 0);
final float density = context.getResources().getDisplayMetrics().density;
int defaultShadowHeightInPix = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
mShadowHeight = a.getDimensionPixelSize(
R.styleable.StickyScrollView_stuckShadowHeight,
defaultShadowHeightInPix);
int shadowDrawableRes = a.getResourceId(
R.styleable.StickyScrollView_stuckShadowDrawable, -1);
if (shadowDrawableRes != -1) {
mShadowDrawable = context.getResources().getDrawable(
shadowDrawableRes);
}
a.recycle();
}
/**
* Sets the height of the shadow drawable in pixels.
*
* #param height
*/
public void setShadowHeight(int height) {
mShadowHeight = height;
}
public void setup(){
mStickyViews = new ArrayList<View>();
}
private int getLeftForViewRelativeOnlyChild(View v){
int left = v.getLeft();
while(v.getParent() != getChildAt(0)){
v = (View) v.getParent();
left += v.getLeft();
}
return left;
}
private int getTopForViewRelativeOnlyChild(View v){
int top = v.getTop();
while(v.getParent() != getChildAt(0)){
v = (View) v.getParent();
top += v.getTop();
}
return top;
}
private int getRightForViewRelativeOnlyChild(View v){
int right = v.getRight();
while(v.getParent() != getChildAt(0)){
v = (View) v.getParent();
right += v.getRight();
}
return right;
}
private int getBottomForViewRelativeOnlyChild(View v){
int bottom = v.getBottom();
while(v.getParent() != getChildAt(0)){
v = (View) v.getParent();
bottom += v.getBottom();
}
return bottom;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(!mClipToPaddingHasBeenSet){
mClippingToPadding = true;
}
notifyHierarchyChanged();
}
#Override
public void setClipToPadding(boolean clipToPadding) {
super.setClipToPadding(clipToPadding);
mClippingToPadding = clipToPadding;
mClipToPaddingHasBeenSet = true;
}
#Override
public void addView(View child) {
super.addView(child);
findStickyViews(child);
}
#Override
public void addView(View child, int index) {
super.addView(child, index);
findStickyViews(child);
}
#Override
public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
super.addView(child, index, params);
findStickyViews(child);
}
#Override
public void addView(View child, int width, int height) {
super.addView(child, width, height);
findStickyViews(child);
}
#Override
public void addView(View child, android.view.ViewGroup.LayoutParams params) {
super.addView(child, params);
findStickyViews(child);
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if(mCurrentlyStickingView != null){
canvas.save();
canvas.translate(getPaddingLeft() + mStickyViewLeftOffset, getScrollY() + mStickyViewTopOffset + (mClippingToPadding ? getPaddingTop() : 0));
canvas.clipRect(0, (mClippingToPadding ? -mStickyViewTopOffset : 0), getWidth() - mStickyViewLeftOffset,mCurrentlyStickingView.getHeight() + mShadowHeight + 1);
if (mShadowDrawable != null) {
int left = 0;
int right = mCurrentlyStickingView.getWidth();
int top = mCurrentlyStickingView.getHeight();
int bottom = mCurrentlyStickingView.getHeight() + mShadowHeight;
mShadowDrawable.setBounds(left, top, right, bottom);
mShadowDrawable.draw(canvas);
}
canvas.clipRect(0, (mClippingToPadding ? -mStickyViewTopOffset : 0), getWidth(), mCurrentlyStickingView.getHeight());
if(getStringTagForView(mCurrentlyStickingView).contains(FLAG_HASTRANSPARANCY)){
showView(mCurrentlyStickingView);
mCurrentlyStickingView.draw(canvas);
hideView(mCurrentlyStickingView);
}else{
mCurrentlyStickingView.draw(canvas);
}
canvas.restore();
}
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(ev.getAction()==MotionEvent.ACTION_DOWN){
mRedirectTouchesToStickyView = true;
}
if(mRedirectTouchesToStickyView){
mRedirectTouchesToStickyView = mCurrentlyStickingView != null;
if(mRedirectTouchesToStickyView){
mRedirectTouchesToStickyView =
ev.getY()<=(mCurrentlyStickingView.getHeight()+ mStickyViewTopOffset) &&
ev.getX() >= getLeftForViewRelativeOnlyChild(mCurrentlyStickingView) &&
ev.getX() <= getRightForViewRelativeOnlyChild(mCurrentlyStickingView);
}
}else if(mCurrentlyStickingView == null){
mRedirectTouchesToStickyView = false;
}
if(mRedirectTouchesToStickyView){
ev.offsetLocation(0, -1*((getScrollY() + mStickyViewTopOffset) - getTopForViewRelativeOnlyChild(mCurrentlyStickingView)));
}
return super.dispatchTouchEvent(ev);
}
private boolean hasNotDoneActionDown = true;
#Override
public boolean onTouchEvent(MotionEvent ev) {
if(mRedirectTouchesToStickyView){
ev.offsetLocation(0, ((getScrollY() + mStickyViewTopOffset) - getTopForViewRelativeOnlyChild(mCurrentlyStickingView)));
}
if(ev.getAction()==MotionEvent.ACTION_DOWN){
hasNotDoneActionDown = false;
}
if(hasNotDoneActionDown){
MotionEvent down = MotionEvent.obtain(ev);
down.setAction(MotionEvent.ACTION_DOWN);
super.onTouchEvent(down);
hasNotDoneActionDown = false;
}
if(ev.getAction()==MotionEvent.ACTION_UP || ev.getAction()==MotionEvent.ACTION_CANCEL){
hasNotDoneActionDown = true;
}
return super.onTouchEvent(ev);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
doTheStickyThing();
}
private void doTheStickyThing() {
View viewThatShouldStick = null;
View approachingStickyView = null;
for(View v : mStickyViews){
int viewTop = getTopForViewRelativeOnlyChild(v) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop());
if(viewTop<=0){
if(viewThatShouldStick==null || viewTop>(getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop()))){
viewThatShouldStick = v;
}
}else{
if(approachingStickyView == null || viewTop<(getTopForViewRelativeOnlyChild(approachingStickyView) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop()))){
approachingStickyView = v;
}
}
}
if(viewThatShouldStick!=null){
mStickyViewTopOffset = approachingStickyView == null ? 0 : Math.min(0, getTopForViewRelativeOnlyChild(approachingStickyView) - getScrollY() + (mClippingToPadding ? 0 : getPaddingTop()) - viewThatShouldStick.getHeight());
if(viewThatShouldStick != mCurrentlyStickingView){
if(mCurrentlyStickingView !=null){
stopStickingCurrentlyStickingView();
}
// only compute the left offset when we start sticking.
mStickyViewLeftOffset = getLeftForViewRelativeOnlyChild(viewThatShouldStick);
startStickingView(viewThatShouldStick);
}
}else if(mCurrentlyStickingView !=null){
stopStickingCurrentlyStickingView();
}
}
private void startStickingView(View viewThatShouldStick) {
mCurrentlyStickingView = viewThatShouldStick;
if(getStringTagForView(mCurrentlyStickingView).contains(FLAG_HASTRANSPARANCY)){
hideView(mCurrentlyStickingView);
}
if(((String) mCurrentlyStickingView.getTag()).contains(FLAG_NONCONSTANT)){
post(mInvalidateRunnable);
}
}
private void stopStickingCurrentlyStickingView() {
if(getStringTagForView(mCurrentlyStickingView).contains(FLAG_HASTRANSPARANCY)){
showView(mCurrentlyStickingView);
}
mCurrentlyStickingView = null;
removeCallbacks(mInvalidateRunnable);
}
/**
* Notify that the sticky attribute has been added or removed from one or more views in the View hierarchy
*/
public void notifyStickyAttributeChanged(){
notifyHierarchyChanged();
}
private void notifyHierarchyChanged(){
if(mCurrentlyStickingView !=null){
stopStickingCurrentlyStickingView();
}
mStickyViews.clear();
findStickyViews(getChildAt(0));
doTheStickyThing();
invalidate();
}
private void findStickyViews(View v) {
if(v instanceof ViewGroup){
ViewGroup vg = (ViewGroup)v;
for(int i = 0 ; i<vg.getChildCount() ; i++){
String tag = getStringTagForView(vg.getChildAt(i));
if(tag!=null && tag.contains(STICKY_TAG)){
mStickyViews.add(vg.getChildAt(i));
}else if(vg.getChildAt(i) instanceof ViewGroup){
findStickyViews(vg.getChildAt(i));
}
}
}else{
String tag = (String) v.getTag();
if(tag!=null && tag.contains(STICKY_TAG)){
mStickyViews.add(v);
}
}
}
private String getStringTagForView(View v){
Object tagObject = v.getTag();
return String.valueOf(tagObject);
}
private void hideView(View v) {
if(Build.VERSION.SDK_INT>=11){
v.setAlpha(0);
}else{
AlphaAnimation anim = new AlphaAnimation(1, 0);
anim.setDuration(0);
anim.setFillAfter(true);
v.startAnimation(anim);
}
}
private void showView(View v) {
if(Build.VERSION.SDK_INT>=11){
v.setAlpha(1);
}else{
AlphaAnimation anim = new AlphaAnimation(0, 1);
anim.setDuration(0);
anim.setFillAfter(true);
v.startAnimation(anim);
}
}
}
What I'm trying to do is to adapt it to suit my needs, but I've tried poking around in this implementation to see how it does what it does and I cannot figure out how it gets the view to get stuck to the top of the ScrollView. Does anyone have any idea how this works?
Edit:
Here is the layout that I want to apply this concept too:
*Keep in mind that the Headers (Headers 1 & 2) are custom ViewGroups that contains the Sub-Headers (Header 1a, 1b, 2a); which are also custom ViewGroups that contain custom views which are the Items.
The StickyScrollView you are using is just saving a tag to whether it should be sticky or not and if not which child of scrollview is it's header, and according to that it is maintaining it as a first child view.
If you want to use this StickyScrollView only you have to modify it and maintain one more tag as sub-header.
I will suggest rather using this ScrollView, you can use this ListView. It is very easy to implement and it works.
You can use header-decor for your requirement. Internally its using RecyclerView, so it is advisable to use it. Check Double Header section in below gif.
Hope this will help you.
This isn't rocket science. There's two key parts to understanding this.
First is in the method doTheStickyThing. This figures out what goes where.
The initial step is figuring out which header to stick. Once you scroll down, you have views both above and below the top of the scroll view. You want to stick the bottom-most header that is still above the top of the scroll view. So you see a lot of expressions like this:
getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))
That resulting value is just the offset of the top of the view from the top of the scroll view. If the header is above the top of the scroll view, the value is negative. So it turns out you want the header that has the greatest offset value that is still less than or equal to zero. The winning view gets assigned to viewThatShouldStick.
Now that you have a sticking header, you want to know which following header might start pushing that one out of the way when scrolling. That gets assigned to approachingView
If the approaching view is pushing the header out of the way, you have to offset the top of the header. That value is assigned to stickyViewTopOffset
The second key part is drawing the header. That's done in dispatchDraw.
Here's the trick to making the view look "stuck": The normal rendering logic would like to put that header at a certain place based on its current bounds. We can just move the canvas (translate) underneath that header so that it draws at the top of the scroll view instead of wherever it would normally draw. Then we tell the view to draw itself. This happens after all the list item views have been already been drawn, so the header appears to float on top of the list items.
When we move the canvas around, we also have to take into account the case where another approaching header is starting to push this one out of the way. The clipping handles some corner cases concerning how things should look when paddings are involved.
I started working on modifying the code to do what you wanted, but things got complicated fast.
Instead of tracking two headers, you need to track three headers: header, subheader, and approaching header. Now you have to handle the top offset of the subheader along with the top offset of the header. And then you have two scenarios: First is that the approaching header is a main header. This is going to modify both top offsets. But when the approaching header is a subheader, only the top offset of the pinned subheader is affected, and the main header offset stays the same.
I can get this, but I'm short on time right now. I'll finish off the code and post it if I can find the time.

ScrollView with multiple LinearLayouts doesn't size correctly

I have created a GridView control, which inhertis from a ScrollView, the idea of this control, is that it will contain multiple Views arranged in a grid format with a given number of columns and rows.
When the view is first built, the GridView doesn't know the size of its container, so I wait until the onSizeChanged method is called, then I apply the relevant sizing.
When the below is run, it doesn't re-size the grid to show it correctly, each control is only as big as it needs to be to show the text.
When the `onSizeChanged' method is called, it has the correct size, and applies the correct size to each child view, but it doesn't affect the way the controls are drawn (i.e. they're still all bunched up on the top left of the screen).
Despite this, I have actually got it working, but it draws twice. I do this by creating a Runnable which calls ResizeList. Then calling new Handler().post(r) straight after I call BuildIt.
Although this is a solution, I just don't understand why it doesn't work in the below form.
Incidentally, if the GridView is the main View added to the Activity, it displays fine, it's only when it's subsequently added. Which is why I have the Button, which you have to press to show the grid.
Can anyone suggest why the below code doesn't work properly?
public class MainActivity extends Activity {
GridView sv;
FrameLayout flay;
#Override protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
flay=new FrameLayout(this);
this.setContentView(flay);
Button b=new Button(this);
b.setText("press me to show grid view");
b.setOnClickListener(ocl);
flay.addView(b);
}
OnClickListener ocl=new OnClickListener()
{
#Override public void onClick(View v)
{
BuildIt();
}};
private void BuildIt()
{
flay.removeAllViews(); // remove the button control
sv=new GridView(this);
for (int c1=0 ; c1<30 ; c1++)
{
TextView tv=new TextView(this);
tv.setText("Item "+c1);
tv.setGravity(android.view.Gravity.CENTER);
sv.addListItem(tv);
}
flay.addView(sv);
sv.ConstructList();
}
}
The GridView class
public class GridView extends ScrollView
{
final int rows=4;
final int cols=4;
private ArrayList<View> allViews=new ArrayList<View>();
private LinearLayout ll;
public GridView(Context context)
{
super(context);
ll=new LinearLayout(context);
ll.setOrientation(LinearLayout.VERTICAL);
this.addView(ll);
}
public void addListItem(View v)
{
allViews.add(v);
}
public void ConstructList()
{
int c1=0;
ll.removeAllViews(); // Just in case we're re-building
LinearLayout row=null;
for (View v : allViews)
{
if (c1%cols==0)
{
row=new LinearLayout(this.getContext());
ll.addView(row);
}
row.addView(v);
c1++;
}
}
private void ResizeList()
{
int useHeight=getHeight()/rows;
int useWidth=getWidth()/cols;
LinearLayout.LayoutParams lpCol=new LinearLayout.LayoutParams(useWidth, useHeight);
Log.i("log","About to set width/height="+useWidth+"/"+useHeight);
int numKids= ll.getChildCount();
for (int c1=0 ; c1<numKids ; c1++)
{
LinearLayout ll2=(LinearLayout)ll.getChildAt(c1);
for (int c2=0 ; c2<ll2.getChildCount() ; c2++) // use getChildCount rather than cols, just in case it's the last one
{
View v=ll2.getChildAt(c2);
v.setLayoutParams(lpCol);
}
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
ResizeList();
}
}
I have a function which is used to resize the child's width and height in gridView.
May be this could help you :
public static void setGridChild_Height(GridView gridView, int columns) {
ListAdapter listAdapter = gridView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
int items = listAdapter.getCount();
int rows = 0;
for (int j = 0; j < items; j++) {
View view = gridView.getChildAt(j);
if (view != null && view.getHeight() > totalHeight) {
totalHeight = view.getHeight();
}
}
System.out.println("totalHeight -> " + totalHeight);
if (totalHeight > 0) {
for (int j = 0; j < items; j++) {
View view = gridView.getChildAt(j);
if (view != null && view.getHeight() < totalHeight) {
view.setMinimumHeight(totalHeight);
}
}
}
// View listItem = listAdapter.getView(0, null, gridView);
// listItem.measure(0, 0);
// totalHeight = listItem.getMeasuredHeight();
//
// float x = 1;
// if (items > columns) {
// x = items / columns;
// rows = (int) (x + 1);
// totalHeight *= rows;
// }
//
// ViewGroup.LayoutParams params = gridView.getLayoutParams();
// params.height = totalHeight;
// gridView.setLayoutParams(params);
}
Try to change logic as per your requirement.
Code is not tested perfectly.
It's because onSizeChanged when newly added to the view hierarchy uses it's old sizes of "0" (according to the docs: http://developer.android.com/reference/android/view/View.html#onSizeChanged(int, int, int, int))
I think what you want is to a addOnLayoutChangedListener : http://developer.android.com/reference/android/view/View.html#addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener)
Using the ViewTreeObserver might be another option that will work for you: How can you tell when a layout has been drawn?

Synchronize Scroll position of ScrollView across all fragments inside ViewPager

So I have an activity with a view pager that inflates about 7 Fragments, and each fragment has a ScrollView inside it, I want to synchronize the scroll of all 7 scrollviews, so if the user scrolls to a position on fragment 3 and moves on to fragment 5 they should be at the exact same position, the scrollviews inflate the same children so the total height of the scrollview is constant throughout all 7 fragments.
I go some kind of system working, but its kind of a hit or miss, i get the scroll position of a list and broadcast the position and the fragment that is idle consumes the broadcast, and I save the position on the activity too so that the position can be requested in onResume of fragments that have not been inflated yet. Here is my code:
#Override
public void onResume() {
super.onResume();
mParentScrollView.setScrollY(mParentActivity.getScrollPos());
}
#Override
public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldX, int oldY) {
mScrollListener.onScroll(y);
mParentActivity.setScrollPos(y);
if (callbackSwitch) {
Intent intent = new Intent("ACTION_SCROLL");
intent.putExtra("TRANSLATION", y);
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
}
callbackSwitch = true; //Switch to stop recursive calls
}
private BroadcastReceiver mTranslationReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (!isVisible) {
int translation = intent.getIntExtra("TRANSLATION", 0);
mParentScrollView.setScrollY(translation);
callbackSwitch = false;
}
}
};
I wanted to know if there is a better and stable way of achieving something similar.
Thanks in Advance.
Not sure how elegant this solutions is, but it should do the trick. Extend ViewPager and override onMeasure(int, int) method.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > height) height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
My custom ViewPager was inside of a NestedScrollView and both of child fragments did not contained any additional ScrollViews.

AdapterView onLayout infinite loop

I am trying to make my own list and therefore I am extending the AdapterView class.
I have overridden the onLayout method to add children, measure them and call the layout method.
my problem is that the onLayout method gets called infinitely and items are duplicated on each call.
I have looked on the Internet and verified that my children are not changing (I have made each child returns an empty view with no dynamic content).
here is my code:
protected void onLayout(final boolean changed, final int left, final int top, final int right,
final int bottom)
{
super.onLayout(changed, left, top, right, bottom);
// If we don't have an adapter yet, do nothing and return
if(mAdapter == null)
{
return;
}
fillList();
positionItems();
}
fillList():
private void fillList()
{
int position = 0;
if(mCurrentList*mNumberItemsPerList > mAdapter.getCount() )
{
mCurrentList= 0;
return ;
}
if(mCurrentList < 0)
{
double lastList = (double)(mAdapter.getCount()/mNumberItemsPerList);
mCurrentList= (int) Math.ceil(lastList);
return ;
}
//this.removeAllViewsInLayout();
while( position+mCurrentList*mNumberItemsPerList < mAdapter.getCount() )
{
// Child view
final View child = mAdapter.getView(position+mCurrentList*mNumberItemsPerList, null, this);
// Add the child and measure its dimensions to calculate the remaining space
addAndMeasureChild(child);
position++;
}
}
addAndMeasureChild():
private void addAndMeasureChild(View child)
{
LayoutParams params = child.getLayoutParams();
if(params == null)
{
params = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
}
//child.setLayoutParams(params);
addViewInLayout(child,-1, params);
// measure the dimensions
child.measure(MeasureSpec.EXACTLY | 250,MeasureSpec.EXACTLY | 250);
}
positionItems():
private void positionItems()
{
int left = 0;
int middleItem = mNumberItemsPerList / 2;
for(int index =0; index < getChildCount(); index++)
{
View child = getChildAt(index);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
int bottom= (getHeight()-height)/2;
if(index < middleItem)
{
child.layout(left+15, 70+(middleItem-index)*30, left+width, 70+(middleItem-index)*30+height);
}
if(index == middleItem)
{
child.layout(left+15, 70, left+width, 70+height);
}
if(index > middleItem)
{
int diff = index -middleItem;
child.layout(left+15, 70+(middleItem-(index - (middleItem*diff)))*30, left+width, 70+(middleItem-(index - (middleItem*diff)))*30+height);
}
left += width;
}
}
and this is the getView of my child:
public View getView(final int position, final View convertView, final ViewGroup parent)
{
View view = convertView;
if (view == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.cat_item_layout, null);
}
return view;
}
I've figured out what was causing the infinite call to onLayout.
on my main layout I have added a clock with this custom view inside a relative layout.
I don't know why but the clock (which is updated every second) causes my view to call onLayout again.

Categories

Resources