22

私は次のようImageViewにaの高さと幅を取得しようとFragmentしていますViewTreeObserver

import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;

private ImageView imageViewPicture;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_general_activity_add_recipe, container, false);
    setHasOptionsMenu(true);

    ...

    final ViewTreeObserver observer = imageViewPicture.getViewTreeObserver();
    observer.addOnGlobalLayoutListener (new OnGlobalLayoutListener () {
        @Override public void onGlobalLayout() {
            observer.removeGlobalOnLayoutListener(this);
        }
    });

    return view;
}

このコードを実行すると、次の例外が発生します。

10-12 23:45:26.145: E/AndroidRuntime(12592): FATAL EXCEPTION: main
10-12 23:45:26.145: E/AndroidRuntime(12592): java.lang.IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.view.ViewTreeObserver.checkIsAlive(ViewTreeObserver.java:509)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.view.ViewTreeObserver.removeGlobalOnLayoutListener(ViewTreeObserver.java:356)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at com.thimmey.rezepte.AddRecipeActivity_GeneralFragment$1.onGlobalLayout(AddActivity_GeneralFragment.java:83)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:566)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1736)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2644)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.os.Handler.dispatchMessage(Handler.java:99)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.os.Looper.loop(Looper.java:137)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at android.app.ActivityThread.main(ActivityThread.java:4517)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at java.lang.reflect.Method.invokeNative(Native Method)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at java.lang.reflect.Method.invoke(Method.java:511)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:993)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:760)
10-12 23:45:26.145: E/AndroidRuntime(12592):     at dalvik.system.NativeStart.main(Native Method)

ドキュメントには、これremoveGlobalOnLayoutListenerは非推奨であると記載されていますがremoveOnGlobalLayoutListener、提案されているようにを使用すると、未定義のエラーが発生します。

私が間違っていることは何ですか?

4

3 に答える 3

71

これを試して :

   ViewTreeObserver observer = imageViewPicture.getViewTreeObserver();
   observer.addOnGlobalLayoutListener (new OnGlobalLayoutListener () {
    @Override
     public void onGlobalLayout() {

       imageViewPicture.getViewTreeObserver().removeGlobalOnLayoutListener(this);
      }
    });
于 2012-10-12T22:15:32.833 に答える
20

他の解決策は問題なく機能しますが、これが発生している理由は説明されていません。

ここで対処するいくつかの問題があります:

GlobalOnVSOnGlobal

GlobalOnバージョンを使用すると、非推奨の警告が表示されますが、ソースを確認すると、OnGlobalバージョンを呼び出しているだけなので、同等です。違いは、APIレベル16のOnGlobalのものしか使用できないことです。したがって、以前のバージョンをターゲットにしている場合は、GlobalOnを使用して、その非推奨の警告に対処する必要があります。

なぜ死んだのですか?

この質問は、ビュー階層にまだアタッチされていないonCreateView、のコードに関するものであることに注意してください。imageViewPictureざっと見てみるとView.getViewTreeObserver()、この場合は「フローティング」オブザーバーが作成されていることがわかります。次に、ビューが階層に配置さdispatchAttachedToWindowれると呼び出され、ウィンドウとビューのフローティングオブザーバーがマージされます。

info.mTreeObserver.merge(mFloatingTreeObserver);

これにより、登録されているすべてのリスナーがウィンドウのオブザーバーに移動し、フローティングオブザーバーが強制終了されます。

取得した元のオブザーバーはフローティングオブザーバーであり、デッドであるため、追加/削除を呼び出すと上記の例外が発生します。

彼らは同じオブザーバーですか?

Danyalとして、私はなぜremoveGlobalOnLayoutListener別のオブザーバーで動作するのか戸惑いましたが、一時的なフローティングオブザーバーで明らかになりました。フローティングの1つがウィンドウのオブザーバーにマージされると、リスナーは他のオブザーバーに移動します。したがって、後で呼び出すView.getViewTreeObserver()と、リスナーを含むオブザーバーが得られます。これで、新しいオブザーバーがリスナーの処理を担当します。

しかし、それは時々動作します!、そして別の解決策

多くの場合、ローカル(クロージャ)変数を保持してもよい理由に関するZordidのコメントについては、同様の理由で説明できます。で膨らんだばかりのビューは、onCreateView返された後、まだ少しだけアタッチされていません。あなたが見たほとんどは、おそらくonCreateViewライフサイクルの後の方法にあります。floatの(OP)ソリューションは、オブザーバー関連のコードがに含まれている場合は問題なく機能しonViewCreatedます。すべてのライフサイクルメソッドには独自の責任があるため、次のようにコードを分割することをお勧めします。

private ImageView imageViewPicture;

@Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);
}

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_general_activity_add_recipe, container, false);

    // assuming ... includes:
    this.imageViewPicture = view.findViewById(R.id.image);

    return view;
}

@Override public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    final ViewTreeObserver observer = imageViewPicture.getViewTreeObserver();
    observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener () {
        @Override public void onGlobalLayout() {
            observer.removeGlobalOnLayoutListener(this);
        }
    });
}

また、他のリスナーをに接続するのが好きですonViewCreated。これにより、インスタンス変数の数が最小化imageViewPictureされるため、ローカル変数になります。

同じことがおそらく当てはまりActivity.setContentView、膨張したビューをすぐにアタッチし、通常は呼び出されるonCreateため、オブザーバー/リスナーと遊ぶときまでに階層がライブになります。

于 2015-03-20T17:27:52.330 に答える
3

agetViewTreeObserverが生きているかどうかを確認することをお勧めします。次のコードでうまくいくと思います。https://stackoverflow.com/a/15301092/2914140、https://stackoverflow.com/a/26193736/2914140などに基づいています

** アップデート **

ViewTreeObserverからリスナーを削除を読んだ後、https: //stackoverflow.com/a/40013262/2914140少し書き直しました。

public static void captureGlobalLayout(@NonNull final View view,
                                       @NonNull final ViewTreeObserver.OnGlobalLayoutListener listener) {
    ViewTreeObserver vto = view.getViewTreeObserver();
    if (vto.isAlive()) {
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        ViewTreeObserver vto = view.getViewTreeObserver();
                        if (vto.isAlive()) {
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                                vto.removeOnGlobalLayoutListener(this);
                            } else {
                                //noinspection deprecation
                                vto.removeGlobalOnLayoutListener(this);
                            }
                            listener.onGlobalLayout();
                        }
                    }
                });
    } else {
        view.post(new Runnable() {
            @Override
            public void run() {
                listener.onGlobalLayout();
            }
        });

    }
}

**古い答え**

奇妙ですが view.getViewTreeObserver().isAlive()、trueの場合でも、次にview.getViewTreeObserver()を呼び出すと、同じ例外が発生する可能性があります。そこで、コードをtry-catchで囲みました。おそらく、これをすべてスキップして、view.post(...)ブロックのみを保持することができます。

if (view.getViewTreeObserver().isAlive()) {
    try {
        view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (view.getViewTreeObserver().isAlive()) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                        view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    } else {
                        view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }
                }
                yourCode();
            }
        });
    } catch (IllegalStateException e) {
        e.printStackTrace();
        // The same as below branch.
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                view.post(new Runnable() {
                    @Override
                    public void run() {
                        yourCode();
                    }
                });
            }
        });
    }
} else {
    getActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {
            // Try to wait until the view is ready.
            view.post(new Runnable() {
                @Override
                public void run() {
                    yourCode();
                }
            });
        }
    });
}

おそらくview.post(...)十分ですが、私はバックグラウンドスレッドから呼び出したので、同じことを行う場合は、から呼び出す方がよいでしょうrunOnUiThread(new Runnable() ...

于 2016-08-17T16:20:38.597 に答える