2011-08-10 3 views
16

사용자 지정보기에 대한 크기 조정 및 레이아웃 문제가 계속 발생하며 누구나 "모범 사례"접근 방식을 제안 할 수 있는지 궁금합니다. 문제는 다음과 같습니다. 내용에 필요한 높이가보기의 너비에 따라 달라지는 사용자 정의보기를 상상해보십시오 (여러 줄로 된 TextView와 비슷 함). (높이가 레이아웃 매개 변수로 고정되어 있지 않은 경우에만이 기능이 적용됩니다.) 캐치 (catch)는 지정된 너비에 대해 이러한 사용자 정의보기에서 내용 높이를 계산하는 것이 비용이 많이 듭니다. 특히, UI 스레드에서 계산하기에는 너무 비쌉니다. 따라서 어느 시점에 작업자 스레드를 실행하여 레이아웃을 계산해야하며 완료 될 때 UI를 업데이트해야합니다.값 비싼보기 높이 계산을 다루는 모범 사례?

질문은 어떻게 설계해야합니까? 몇 가지 전략을 생각해 봤습니다. 그들은 모두 높이가 계산 될 때마다 해당 너비가 기록된다고 가정합니다.

첫번째 전략은이 코드에 나타내

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    int width = measureWidth(widthMeasureSpec); 
    setMeasuredDimension(width, measureHeight(heightMeasureSpec, width)); 
} 

private int measureWidth(int widthMeasureSpec) { 
    // irrelevant to this problem 
} 

private int measureHeight(int heightMeasureSpec, int width) { 
    int result; 
    int specMode = MeasureSpec.getMode(measureSpec); 
    int specSize = MeasureSpec.getSize(measureSpec); 
    if (specMode == MeasureSpec.EXACTLY) { 
     result = specSize; 
    } else { 
     if (width != mLastWidth) { 
      interruptAnyExistingLayoutThread(); 
      mLastWidth = width; 
      mLayoutHeight = DEFAULT_HEIGHT; 
      startNewLayoutThread(); 
     } 
     result = mLayoutHeight; 
     if (specMode == MeasureSpec.AT_MOST && result > specSize) { 
      result = specSize; 
     } 
    } 
    return result; 
} 

레이아웃 나사 마감, 그것은 계산 높이 mLayoutHeight 설정하고 requestLayout() (및 invalidate())를 호출하기 위해 UI 스레드에의 Runnable 게시물 때.

두 번째 전략은 mLayoutHeight에 항상 현재 값을 사용하는 것입니다 (레이아웃 스레드를 실행하지 않고). 폭의 변경을 테스트하고 레이아웃 스레드를 실행하는 것은 onSizeChanged을 무시하여 수행됩니다.

세 번째 전략은 게으르며 onDraw에서 (필요한 경우) 레이아웃 스레드를 시작하는 것입니다.

가능한 한 빨리 필요한 높이를 계산하면서 레이아웃 스레드가 실행 및/또는 종료되는 횟수를 최소화하고 싶습니다. 전화 번호를 requestLayout()으로 최소화하는 것이 좋습니다.

문서에서 알 수 있듯이 onMeasure은 단일 레이아웃 과정에서 여러 번 호출 될 수 있습니다. onSizeChanged이 여러 번 호출 될 수 있다는 점은 분명하지 않지만 가능성이 높습니다. 따라서 논리를 onDraw에 넣는 것이 더 나은 전략 일 수 있다고 생각합니다. 그러나 이는 사용자 정의보기 크기 조정의 정신에 반하는 것으로 보이므로 이에 대해 필자는 비합리적으로 편향되어 있습니다.

다른 사람들도 이와 동일한 문제에 직면했을 것입니다. 제가 놓친 접근법이 있습니까? 가장 좋은 방법이 있습니까?

+0

높이가 필요한보기에서 OnGlobalLayoutListener()를 사용할 수 없습니까? – lokoko

+0

@ lokoko - 나는 그 제안을 이해하고 있는지 잘 모르겠다. 당신은 정교 할 수 있습니까? 문제는 다음과 같습니다. 비싼 레이아웃 높이 계산을 언제 시작해야합니까? –

답변

9

저는 안드로이드의 레이아웃 시스템이 정말로 이런 문제를 해결하도록 설계되지 않았다고 생각합니다. 아마도이 문제를 변경하는 것이 좋습니다.

그렇다면 여기에 핵심적인 문제는보기가 실제로 자체 높이를 계산하지 않아도된다는 것입니다. 항상 뷰의 부모로, 아이의 크기를 계산합니다. 그들은 그들의 "의견"을 말할 수는 있지만 결국 실제 생활과 마찬가지로이 문제에 대해 진정한 진정한 언급을하지는 못합니다.

이는 뷰의 부모 또는 오히려 차원이 하위의 차원과 독립적 인 첫 번째 부모를 살펴볼 것을 제안합니다. 그 부모는 모든 어린이들이 측정 단계를 마칠 때까지 (별도의 실에서 발생) 배치를 거부 할 수 있습니다. 그들이 가지고있는 즉시, 부모는 새로운 레이아웃 단계를 요청하고 그것들을 다시 측정 할 필요없이 아이들을 배치합니다.

어린이의 측정이 부모의 측정에 영향을 미치지 않아 자녀를 다시 측정하지 않고 두 번째 레이아웃 단계를 "흡수"하여 레이아웃 과정을 정정하는 것이 중요합니다.

[편집] 이 부분을 확장하면 아주 사소한 단점이있는 매우 간단한 솔루션을 생각할 수 있습니다. ViewGroup으로 확장되고 ScrollView과 비슷한 AsyncView을 만들면 항상 전체 공간을 채우는 단일 하위 만 포함됩니다. AsyncView은 자체 크기에 대한 하위 측정을 고려하지 않으며 사용 가능한 공간을 채우는 것이 이상적입니다. 모두 AsyncView은 자식의 측정 호출을 별도의 스레드에 랩핑하며, 측정이 완료되면 곧바로 뷰를 호출합니다.

다른보기를 포함하여 원하는 내용을 넣을 수 있습니다. "문제있는 관점"이 계층 구조에 얼마나 깊이 있는지는 중요하지 않습니다. 유일한 단점은 모든 자손이 측정 될 때까지 자손이 렌더링되지 않을 것입니다. 그러나 어쨌든 뷰가 준비 될 때까지 어떤 종류의 애니메이션을 보여주기를 원할 것입니다.

"문제가있는보기"는 어떤 방식 으로든 멀티 스레딩에 대해 신경 쓸 필요가 없습니다. 그것은 다른보기와 마찬가지로 스스로를 측정 할 수 있으며 필요에 따라 많은 시간을 할애 할 수 있습니다.

[EDIT2] 내가 함께 빠른 구현 해킹 귀찮게 :

:

package com.example.asyncview; 

import android.content.Context; 
import android.os.AsyncTask; 
import android.util.AttributeSet; 
import android.view.View; 
import android.view.ViewGroup; 

public class AsyncView extends ViewGroup { 
    private AsyncTask<Void, Void, Void> mMeasureTask; 
    private boolean mMeasured = false; 

    public AsyncView(Context context) { 
     super(context); 
    } 

    public AsyncView(Context context, AttributeSet attrs) { 
     super(context, attrs); 
    } 

    @Override 
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 
     for(int i=0; i < getChildCount(); i++) { 
      View child = getChildAt(i); 
      child.layout(0, 0, child.getMeasuredWidth(), getMeasuredHeight()); 
     } 
    } 

    @Override 
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { 
     super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
     if(mMeasured) 
      return; 
     if(mMeasureTask == null) { 
      mMeasureTask = new AsyncTask<Void, Void, Void>() { 
       @Override 
       protected Void doInBackground(Void... objects) { 
        for(int i=0; i < getChildCount(); i++) { 
         measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec); 
        } 
        return null; 
       } 

       @Override 
       protected void onPostExecute(Void aVoid) { 
        mMeasured = true; 
        mMeasureTask = null; 
        requestLayout(); 
       } 
      }; 
      mMeasureTask.execute(); 
     } 
    } 

    @Override 
    protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
     if(mMeasureTask != null) { 
      mMeasureTask.cancel(true); 
      mMeasureTask = null; 
     } 
     mMeasured = false; 
     super.onSizeChanged(w, h, oldw, oldh); 
    } 
} 

가 작동 예를

+0

이것은 매우 유용합니다. 이는 사용자 정의보기를 사용할 수있는 위치를 제한합니다 (다루는 대상을 인식하는 레이아웃에서만). "인식 레이아웃"이 맞춤 레이아웃의 바로 상위가 아닌 경우 매우 복잡 할 수도 있습니다. 그러나, 나는 그것이 처리 될 수 있다고 생각합니다. 또한 측정 계산 스레드를 시작할 때의 문제를 단순화합니다. 단점은 주식 레이아웃 클래스는 일반적으로 사용할 수 없다는 것입니다. 여기 음식에 대한 많은 생각. 감사. –

+0

답변이 조금 더 확대되었습니다. –

0

또한이 전략과 같은 작업을 수행 할 수 있습니다에 대한 https://github.com/wetblanket/AsyncView 참조 맞춤 하위보기 만들기 :

public class CustomChildView extends View 
{ 
    MyOnResizeListener orl = null; 
    public CustomChildView(Context context) 
    { 
     super(context); 
    } 
    public CustomChildView(Context context, AttributeSet attrs) 
    { 
     super(context, attrs); 
    } 
    public CustomChildView(Context context, AttributeSet attrs, int defStyle) 
    { 
     super(context, attrs, defStyle); 
    } 

    public void SetOnResizeListener(MyOnResizeListener myOnResizeListener) 
    { 
     orl = myOnResizeListener; 
    } 

    @Override 
    protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) 
    { 
     super.onSizeChanged(xNew, yNew, xOld, yOld); 

     if(orl != null) 
     { 
      orl.OnResize(this.getId(), xNew, yNew, xOld, yOld); 
     } 
    } 
} 

와 같은 일부 사용자 정의 리스너 생성 : 당신이 좋아하는 청취자의 인스턴스를

public class MyOnResizeListener 
{ 
    public MyOnResizeListener(){} 

    public void OnResize(int id, int xNew, int yNew, int xOld, int yOld){} 
} 

:

Class MyActivity extends Activity 
{ 
     /***Stuff***/ 

    MyOnResizeListener orlResized = new MyOnResizeListener() 
    { 
      @Override 
      public void OnResize(int id, int xNew, int yNew, int xOld, int yOld) 
      { 
/***Handle resize event and call your measureHeight(int heightMeasureSpec, int width) method here****/ 
      } 
    }; 
} 

을 그리고 사용자 정의보기로 청취자를 통과하는 것을 잊지 마세요 :

/***Probably in your activity's onCreate***/ 
((CustomChildView)findViewById(R.id.customChildView)).SetOnResizeListener(orlResized); 

마지막으로 다음과 같이하여 CustomChildView를 XML 레이아웃에 추가 할 수 있습니다.

<com.demo.CustomChildView> 
     <!-- Attributes --> 
<com.demo.CustomChildView/> 
+0

여기에 표시하려고하거나 무엇을 증명하려고합니까? – lokoko

+0

이것이 어떻게 도움이되는지 잘 모르겠습니다. 나는 커스텀 레이아웃의 크기 변화에 반응하는 것에 관심이 없다. 문제는 사용자 정의보기의 _ 측정 된 크기를 설정하는 방법입니다 (크기가 실제로보기의 부모에 의해 설정되기 전에 레이아웃 프로세스의 일부로 발생해야하는 사항). –

0

Android는 시작시 실제 크기를 알지 못하므로 계산해야합니다. 완료되면 onSizeChanged()가 실제 크기로 알려드립니다.

onSizeChanged() 계산 된대로 크기가 한 번 호출됩니다. 이벤트는 항상 사용자로부터 오지 않아도됩니다. 안드로이드가 크기를 변경하면 onSizeChanged()이 호출됩니다. 그리고 onDraw()과 같은 것을 그릴 때 onDraw()이 호출되어야합니다.

onMeasure()이 그런데 measure()Some other related link for you

에 대한 호출 후 자동으로 권리라고하며, 여기에 같은 흥미로운 질문을 주셔서 감사합니다. 도와 드리겠습니다.

1

값 비싼 계산에 대한 일반적인 접근 방식은 memoization입니다. 계산 결과를 캐싱하여 결과를 다시 사용할 수 있기를 바랍니다. 동일한 입력 번호가 여러 번 나타날 수 있는지 모르기 때문에 메모 작성이 얼마나 잘 적용되는지 모르겠습니다.

+0

예,이 경우 메모 작성이 필수적입니다. 내가 게시 한 코드는 실제로 이름으로 기술을 언급하지는 않았지만 memoization ('mLayoutHeight' 변수)을 사용합니다. –

0

OS 제공 위젯, OS 제공 이벤트/콜백 시스템, OS 제공 레이아웃/제약 조건 관리, 위젯에 OS 제공 데이터 바인딩이 있습니다. 모두 내가 OS 제공 UI API라고 부릅니다. OS 제공 UI API의 품질에 관계없이 앱이 해당 기능을 사용하여 효과적으로 해결되지 않을 수도 있습니다. 그들은 서로 다른 생각을 염두에두고 설계되었을 수도 있기 때문입니다. OS가 제공하는 UI 위에 UI 지향 API를 추상화하고 직접 만들 수있는 좋은 옵션이 항상 있습니다. 자체 제공 api를 사용하면 계산하고 저장 한 다음 다른 위젯 크기를보다 효율적으로 사용할 수 있습니다. 병목 현상을 줄이거 나 제거, 스레딩, 콜백 등 때때로 이러한 layer-api를 만드는 작업은 수분 만에 완료되거나 때로는 전체 프로젝트에서 가장 큰 부분이 될 수 있습니다. 오랜 시간 동안 디버깅하고 지원하려면 약간의 노력이 필요할 수 있습니다. 이는이 방법의 주요 단점입니다. 하지만 장점은 마음이 바뀌는 것입니다. 당신은 "한계를 극복하는 방법을 찾는 방법"대신에 "창조하기가 뜨겁다"고 생각하기 시작합니다. "모범 사례"에 대해 잘 모릅니다. 그러나 이것은 아마도 가장 많이 사용되는 방법 중 하나 일 것입니다. 종종 "우리는 우리 자신의 것을 사용합니다"라고들을 수 있습니다. 제공된자가 제작자와 큰 녀석 중 선택하는 것은 쉽지 않습니다. 그것을 제정신으로 유지하는 것이 중요합니다.