web-dev-qa-db-ja.com

ビューを最初に測定できるのはいつですか?

したがって、表示されているビューの背景ドロアブルを設定しようとすると少し混乱します。コードはビューの高さを知ることに依存しているため、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);
59
kcoppock

私は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()が2回呼び出されましたが、あなたの場合は無限ループを引き起こす可能性があると思います。

TextViewの高さが変わったときにのみsetBackgroundDrawable()を呼び出そうとすることができます:

_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による編集

これがこのコードからやったことです。ゴーティエの答えはこの点に私を導いたので、自分で答えるよりも修正を加えてこの答えを受け入れたいと思います。代わりに、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をチェックしたところ、異常なアクティビティは見られませんでした。うまくいけば、これで終わりです!ありがとう!

69
Gautier Hayoun

ビューツリーオブザーバーについて:

返されたViewTreeObserverオブザーバーは、このビューの存続期間中有効であるとは限りません。このメソッドの呼び出し元が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                                        

            }
        });            
    }

これは私にはかなり脆いようです。まれではありますが、多くのことが間違っている可能性があるようです。

だから私はこれを始めました:ビューにメッセージを投稿すると、メッセージはビューが完全に初期化された後にのみ配信されます(測定中を含む)。測定コードを1回だけ実行し、実際にビューの存続期間中にレイアウトの変更を観察したくない場合(サイズ変更されていないため)、測定コードをこのような投稿に入れます:

 viewToMeasure.post(new Runnable() {

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

    });

ビューの投稿方法に問題はありませんでした。画面のちらつきや、あなたが予想するその他の問題が発生する可能性があります(まだ...)

haveは、グローバルレイアウトオブザーバーが削除されなかったため、またはアクセスしようとしたときにレイアウトオブザーバーが「アライブ」ではなかったため、本番コードでクラッシュしました

64
Splash

グローバルリスナーを使用すると、無限ループに陥ることがあります。

私は電話でこれを修正しました

whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);

リスナー内のonGlobalLayoutメソッドの内部。

11
kevinl

私のプロジェクトでは、次のスニペットを使用しています:

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);
            }
        }
    });
}

そのため、内部で提供されるRunnable#runViewが測定されたことを確認し、関連するコードを実行できます。

3
Dmitry Zaytsev

TextViewのonMeasureをオーバーライドできます。

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
   }
   ...
}

コードを何行も使わずにできることはほぼ確実です。レイヤーリストを作成する機会があると思います。

2

OnPreDrawListener()の代わりにaddOnGlobalLayoutListener()を使用します。以前に呼び出されるためです。

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

OnCreate()メソッドの下のクラスメインアクティビティのメソッドで、すべてのビューメジャーを取得できます。

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

したがって、ビューを再描画、ソート、...できます。 :)

0
nobjta_9x_tq

つまり、ビューのサイズを処理する3つの方法....うーん、多分そうです!

1)@Gautier Hayounが述べたように、OnGlobalLayoutListenerリスナーを使用します。ただし、リスナーの最初のコードlistViewProduct.getViewTreeObserver().removeOnGlobalLayoutListener(this);で呼び出して、このリスナーが無限に呼び出されないようにしてください。もう1つ、ビューが知らない場所に描画されたために、このリスナーが呼び出されない場合があります。したがって、安全のために、onCreate()メソッド(またはフラグメントのonViewCreated())でビューを宣言した直後にこのリスナーを登録しましょう。

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)ビューを描画して表示した直後に何かをしたい場合は、単にpostメソッドを使用します(もちろん、ビューはすでに完全に描画されているため、ここでサイズを取得できます)。例えば、

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

さらに、同じ目的でpostDelayを使用して、少し遅延時間を追加することができます。自分で試してみましょう。いつか必要になります。プロンプトの例では、スクロールビューにアイテムを追加した後、スクロールビューを自動的にスクロールしたり、スクロールしたビューでアニメーション付きの状態からさらにビューを表示したり、いくつかのビューを表示したりする場合(このプロセスはすべてを表示するのに少し時間がかかります) )。スクロールビューのサイズは間違いなく変更されるため、スクロールビューが描画された後、さらにビューを追加した後、正確なサイズになるまで少し待つ必要があります。これは、postDelayメソッドが救助に来るときです!

3)また、独自のビューを設定する場合は、クラスを作成し、ビューのサイズを定義するonMeasure()メソッドを持つ任意のビューから拡張できます。

お役に立てれば、

Mttdat。

0
Nguyen Tan Dat