Monday, November 13, 2017

Why are the subviews out of the bounds of my custom View not drawn?

Leave a Comment

I implement a custom SpinNumberView: it is square shaped (say 40x40), it has a vertical LinearLayout as a subview, within this linear layout are a bunch of 40x40 cells stacked vertically. I want to animate the cells to scroll vertically by changing offsetY of the LinearLayout.

But there is one problem: only the cell initially in bounds (the first) is rendered, the cells outside of the bounds are not drawn, so when I animate the LinearLayout to scroll, the linear layout is spinning, but only the first cell is visible, others are blank spaces. Here is my entire code for the custom View:

public class SpinNumberView extends RelativeLayout { private int startNumber; private int endNumber; private int number; private int gridsize; private int index; public static final double stepDuration = 0.1;  private boolean inAnimation = true; ArrayList<Integer> numbers; public LinearLayout container;  public SpinNumberView(Context context) {     super(context); }  public SpinNumberView(Context context, AttributeSet attrs) {     super(context, attrs); }  @Override protected void dispatchDraw(Canvas canvas) {     // draw the background black solid circle     float radius = (float)(this.gridsize);     Paint p = new Paint();     p.setStyle(Paint.Style.FILL);     p.setARGB(192, 0, 0, 0);     canvas.drawCircle(radius/2, radius/2, radius/2, p);     // draw 1px white border     Paint pp = new Paint();     pp.setStyle(Paint.Style.STROKE);     pp.setStrokeWidth(2.0f);     pp.setARGB(192, 255, 255, 255);     canvas.drawCircle(radius/2, radius/2, radius/2-1, pp);     // clip to the circle     Path path = new Path();     RectF r = new RectF((float)0.0, (float)0.0, radius, radius);     path.addRoundRect(r, radius/2, radius/2, Path.Direction.CW);     canvas.clipPath(path);      super.dispatchDraw(canvas); }  @Override protected void onLayout(boolean b, int i, int i1, int i2, int i3) {     super.onLayout(b, i, i1, i2, i3); }  class AniListener implements Animator.AnimatorListener {     @Override     public void onAnimationStart(Animator animator) {}      @Override     public void onAnimationEnd(Animator animator) {         SpinNumberView.this.animateStep();     }      @Override     public void onAnimationCancel(Animator animator) {}      @Override     public void onAnimationRepeat(Animator animator) {} }  public void animateStep() {     this.container.setTranslationY(0);      float offset;     TimeInterpolator inter;      if(this.inAnimation) {         offset = (float)this.gridsize * this.numbers.size();         inter = new LinearInterpolator();     } else {         offset = (float)this.gridsize * this.index;         inter = new DecelerateInterpolator();     }     long duration = (long)(SpinNumberView.stepDuration * this.numbers.size() * 1000);      ViewPropertyAnimator ani = this.container.animate().translationYBy(-offset).setDuration(duration);     ani.setInterpolator(inter);     if(this.inAnimation) {         ani.setListener(new AniListener());     } else {         ani.setListener(null);     }     ani.start(); }  public void stopAnimation() {     this.inAnimation = false; }  public void startAnimation() {     this.inAnimation = true;     float offset = (float)this.gridsize * this.numbers.size();     long duration = (long)(SpinNumberView.stepDuration * this.numbers.size() * 1000);     ViewPropertyAnimator ani = this.container.animate().translationYBy(-offset).setDuration(duration);     TimeInterpolator inter = new AccelerateInterpolator();     ani.setInterpolator(inter);     ani.setListener(new AniListener());     ani.start(); }  public void setup(int number, int start, int end, int gridsize) {     this.setBackgroundColor(Color.TRANSPARENT);     this.setAlpha((float) 0.5);     this.setClipChildren(false);      this.number = number;     this.startNumber = start;     this.endNumber = end;     this.gridsize = gridsize;     this.numbers = new ArrayList<>();     for(int i=start; i<=end;i++) {         this.numbers.add(i);     }     Collections.shuffle(this.numbers);     // Find index of target number within shuffled array     this.index = this.numbers.indexOf(this.number);      this.container = new LinearLayout(this.getContext());     this.container.setOrientation(LinearLayout.VERTICAL);     this.container.setGravity(Gravity.CENTER_HORIZONTAL);     LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(this.gridsize, this.gridsize * (this.numbers.size()+1));     this.container.setLayoutParams(params);     this.addView(this.container);      int offsety = 0;     // setup all the number views     for(int k=0;k<this.numbers.size()+1;k++) {         String txt;         if(k==this.numbers.size()) {             txt = Integer.toString(this.numbers.get(0));         } else {             txt = Integer.toString(this.numbers.get(k));         }          TextView tv = new TextView(this.getContext());         tv.setLayoutParams(new LayoutParams(this.gridsize, this.gridsize));         tv.setText(txt);         tv.setTextSize(24.0f);         tv.setTextColor(Color.WHITE);         tv.setTextAlignment(TextView.TEXT_ALIGNMENT_CENTER);         tv.setLines(1);         tv.setGravity(Gravity.CENTER_VERTICAL);         this.container.addView(tv);          offsety += this.gridsize;     }      this.invalidate(); } } 

Why is this happening?

BTW: I take a screenshot with getDrawingCache() of screen content, the cells are visible in the screenshot!

3 Answers

Answers 1

Yes! It happend when we get some view height or width of a view. Because didn't completely render the view when we call its height or width yet.

Solution:

Use this code to get Height and width

EditText edt = (EditText) findViewbyid(R.id.tv);  edt.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {             @Override             public void onGlobalLayout() {                 int height= edt.getHeight();                 int width = edt.getHeight();                 edt.getViewTreeObserver().removeOnGlobalLayoutListener(this);             }         }); 

Answers 2

To answer my own question:

When overriding onLayout() function, I need to layout the subviews myself like this:

@Override protected void onLayout(boolean b, int i, int i1, int i2, int i3) {     super.onLayout(b, i, i1, i2, i3);     this.container.layout(0, 0, this.gridsize, this.gridsize * (this.endNumber-this.startNumber+2)); } 

Answers 3

Glad you solved it by yourself, in iOS, we use something like Redraw method for these scenarios. Hopefully it will help you to further optimize your code.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment