IT TIP

언제 처음으로 뷰를 측정 할 수 있습니까?

itqueen 2020. 12. 6. 22:30
반응형

언제 처음으로 뷰를 측정 할 수 있습니까?


그래서 뷰가 표시 될 때 배경 드로어 블을 설정하는 데 약간의 혼란이 있습니다. 코드는 뷰의 높이를 아는 것에 의존하므로 onCreate()또는 에서 호출 할 수 없습니다. 0을 반환 onResume()하기 때문 입니다. 그래도 얻을 수있는 가장 가까운 것 같습니다. 사용자에게 표시 될 때 배경이 변경되도록 아래와 같은 코드를 어디에 넣어야합니까?getHeight()onResume()

    TextView tv = (TextView)findViewById(R.id.image_test);
    LayerDrawable ld = (LayerDrawable)tv.getBackground();
    int height = tv.getHeight(); //when to call this so as not to get 0?
    int topInset = height / 2;
    ld.setLayerInset(1, 0, topInset, 0, 0);
    tv.setBackgroundDrawable(ld);

에 대해 몰랐고 ViewTreeObserver.addOnPreDrawListener()테스트 프로젝트에서 시도했습니다.

코드를 사용하면 다음과 같습니다.

public void onCreate() {
setContentView(R.layout.main);

final TextView tv = (TextView)findViewById(R.id.image_test);
final LayerDrawable ld = (LayerDrawable)tv.getBackground();
final ViewTreeObserver obs = tv.getViewTreeObserver();
obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw () {
        Log.d(TAG, "onPreDraw tv height is " + tv.getHeight()); // bad for performance, remove on production
        int height = tv.getHeight();
        int topInset = height / 2;
        ld.setLayerInset(1, 0, topInset, 0, 0);
        tv.setBackgroundDrawable(ld);

        return true;
   }
});
}

내 테스트 프로젝트에서 onPreDraw()두 번 호출되었으며 귀하의 경우 무한 루프가 발생할 수 있다고 생각합니다.

setBackgroundDrawable()높이가 TextView변경 될 때만 호출 할 수 있습니다 .

private int mLastTvHeight = 0;

public void onCreate() {
setContentView(R.layout.main);

final TextView tv = (TextView)findViewById(R.id.image_test);
final LayerDrawable ld = (LayerDrawable)tv.getBackground();
final ViewTreeObserver obs = mTv.getViewTreeObserver();
obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw () {
        Log.d(TAG, "onPreDraw tv height is " + tv.getHeight()); // bad for performance, remove on production
        int height = tv.getHeight();
        if (height != mLastTvHeight) {
            mLastTvHeight = height;
            int topInset = height / 2;
            ld.setLayerInset(1, 0, topInset, 0, 0);
            tv.setBackgroundDrawable(ld);
        }

        return true;
   }
});
}

그러나 그것은 당신이 달성하려는 것에 대해 약간 복잡하게 들리지만 성능에는 좋지 않습니다.

kcoppock에 의해 편집

이 코드에서 내가 한 작업은 다음과 같습니다. Gautier의 대답은 저를이 시점까지 얻었으므로 직접 대답하기보다는 수정하여이 대답을 받아들이고 싶습니다. 대신 ViewTreeObserver의 addOnGlobalLayoutListener () 메서드를 사용했습니다 (이것은 onCreate ()에 있습니다).

final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        LayerDrawable ld = (LayerDrawable)tv.getBackground();
        ld.setLayerInset(1, 0, tv.getHeight() / 2, 0, 0);
    }
});

완벽하게 작동하는 것 같습니다. LogCat을 확인했지만 비정상적인 활동이 보이지 않았습니다. 바라건대 이것입니다! 감사!


뷰 트리 관찰자와 관련하여 :

반환 된 ViewTreeObserver 옵저버는이 View의 수명 동안 유효하다고 보장되지 않습니다. 이 메서드의 호출자가 ViewTreeObserver에 대한 장기 참조를 유지하는 경우 항상 isAlive ()의 반환 값을 확인해야합니다.

비록 그것의 짧은 수명의 참조 (뷰를 측정하고 당신이하고있는 것과 같은 종류의 일을하기 위해 한 번만 사용됨)하더라도 나는 그것이 더 이상 "살아 있지"않고 예외를 던지는 것을 보았다. 사실, ViewTreeObserver의 모든 함수는 더 이상 '살아있는'상태가 아니면 IllegalStateException을 발생 시키므로 항상 'isAlive'를 확인해야합니다. 나는 이것을해야했다 :

    if(viewToMeasure.getViewTreeObserver().isAlive()) {
        viewToMeasure.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if(viewToMeasure.getViewTreeObserver().isAlive()) {
                    // only need to calculate once, so remove listener
                    viewToMeasure.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }

                // you can get the view's height and width here

                // the listener might not have been 'alive', and so might not have been removed....so you are only
                // 99% sure the code you put here will only run once.  So, you might also want to put some kind
                // of "only run the first time called" guard in here too                                        

            }
        });            
    }

이것은 나에게 꽤 부서지기 쉬운 것 같습니다. 드물기는하지만 많은 것들이 잘못 될 수있는 것 같습니다.

그래서 저는이 작업을 시작했습니다. View에 메시지를 게시하면 메시지는 View가 완전히 초기화 된 후에 만 ​​전달됩니다 (측정 포함). 측정 코드를 한 번만 실행하고 뷰의 수명 동안 레이아웃 변경 사항을 실제로 관찰하고 싶지 않은 경우 (크기가 조정되지 않기 때문에) 다음과 같은 게시물에 측정 코드를 넣습니다. :

 viewToMeasure.post(new Runnable() {

        @Override
        public void run() {
            // safe to get height and width here               
        }

    });

I haven't had any problems with the view posting strategy, and I haven't seen any screen flickers or other things you'd anticipate might go wrong (yet...)

I have had crashes in production code because my global layout observer wasn't removed or because the layout observer wasn't 'alive' when I tried to access it


by using the global listener, you may run into an infinite loop.

i fixed this by calling

whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);

inside of the onGlobalLayout method within the listener.


In my projects I'm using following snippet:

public static void postOnPreDraw(Runnable runnable, View view) {
    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            try {
                runnable.run();
                return true;
            } finally {
                view.getViewTreeObserver().removeOnPreDrawListener(this);
            }
        }
    });
}

So, inside provided Runnable#run you can be sure that View was measured and execute related code.


You can override onMeasure for TextView:

public MyTextView extends TextView {
   ...
   public void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec,heightMeasureSpec); // Important !!!
      final int width = getMeasuredHeight();
      final int height = getMeasuredHeight();
      // do you background stuff
   }
   ...
}

I am almost sure that you can do it without any line of code also. I think there is a chance to create layer-list.


You can get all of view measures in method of class main activity below onCreate() method:

@Override
protected void onCreate(Bundle savedInstanceState){}
@Override
public void onWindowFocusChanged(boolean hasFocus){
    int w = yourTextView.getWidth();
}

So you can redrawing, sorting... your views. :)


Use OnPreDrawListener() instead of addOnGlobalLayoutListener(), because it is called earlier.

  tv.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            tv.getViewTreeObserver().removeOnPreDrawListener(this);
            // put your measurement code here
            return false;
        }
    });

So summarily, 3 ways to handle size of view.... hmmm, maybe so!

1) As @Gautier Hayoun mentioned, using OnGlobalLayoutListener listener. However, please remember to call at the first code in listener: listViewProduct.getViewTreeObserver().removeOnGlobalLayoutListener(this); to make sure this listener won't call infinitely. And one more thing, sometime this listener won't invoke at all, it because your view has been drawn somewhere you don't know. So, to be safe, let's register this listener right after you declare a view in onCreate() method (or onViewCreated() in fragment)

view = ([someView]) findViewById(R.id.[viewId]);

// Get width of view before it's drawn.
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {

        // Make this listener call once.
        view.getViewTreeObserver().removeOnGlobalLayoutListener(this);

        int width = listViewProduct.getWidth();
      });

2) If you want to do something right after the view has been drawn and shown up, just use post method (of course you can get the size here, since your view was drawn completely already). For example,

listViewProduct.post(new Runnable() {
  @Override
  public void run() {
    // Do your stuff here.
  }
});

More over, you can use postDelay with the same purpose, adding a little delay time. Let's try it yourself. You will need it sometime. For prompt example, when you want to scroll the scroll view automatically after adding more items into scroll view or expand or make some more view visible from gone status in scroll view with animation (it means this process will take a little time to show up everything). It will definitely change scroll view's size, so after scroll view is drawn, we also need to wait a little time for get an exact size after it adds more view into it. This's a high time for postDelay method to come to rescue!

3) And if you want to set up your own view, you can create a class and extend from any View you you like which all have onMeasure() method to define size of the view.

Hope this helps,

Mttdat.

참고URL : https://stackoverflow.com/questions/4393612/when-can-i-first-measure-a-view

반응형