如何在其他视图之后绘制特定的视图?

3
我有一个简单的LinearLayout,只包含一个自定义视图扩展TextView(我将称之为“IdiomView”)和一个ListView。 IdiomView与普通的TextView唯一的不同是我已经重写了onDraw()方法,以迭代地减小文本大小,直到文本少于3行。我的问题是当视图被绘制时,用户会看到这个:
 ______________
|__ACTION_BAR__|
|  IdiomView   |
|______________|
|              |
|   ListView   |
|              |
|              |
|______________|

很快就变成了:

 ______________
|__ACTION_BAR__|
|__IdiomView __|
|              |
|   ListView   |
|              |
|              |
|              |
|______________|

即ListView被绘制后,IdiomView排序其大小后会跳动。

我希望有一种方法能够在绘制完整个IdiomView之后再绘制ListView。这篇文章 What event is fired after all views are fully drawn? 解释了如何通过调用View.post(Runnable)在绘制完成后排列线程。但问题在于,我的重写的onDraw()方法多次调用onDraw()以计算较小文本是否覆盖了不到3行,因此在我想要出现ListView之前,该元素可能会多次“完成绘制”。

我很感谢所有的评论和答案。以下是我的当前代码:

布局XML:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:background="@color/off_white"
    android:orientation="vertical" >

    <carter.cwords.idioms.IdiomView
        android:id="@+id/idiom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginBottom="10dp"
        android:textColor="@color/transparent"
        android:textSize="28sp"
        android:textStyle="italic"
        android:visibility="invisible" />

    <ListView
        android:id="@+id/quote_list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:choiceMode="none"
        android:footerDividersEnabled="false"
        android:headerDividersEnabled="false"
        android:visibility="invisible" />

</LinearLayout>

活动:

private IdiomView mIdiomTextView;
private ListView mQuoteList;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.idiom_of_the_day);
    mIdiomTextView = (IdiomView) findViewById(R.id.idiom);
    mQuoteList = (ListView) findViewById(R.id.quote_list);      

    // Populate page data onResume()
}

@Override
protected void onResume() {
    super.onResume();

    sendRequest(R.string.url_idiom_of_the_day, new AfterRequest(){
        @Override
        public void useResults(Document resultXml) {
            if(resultXml != null){

                Log.i(getClass().getSimpleName(), "useResults()");

                String idiomString = XmlUtilities.getTextValue(resultXml, NetworkHelper.XML_TAG_IDIOM_CONTENT);

                logDebug("idiomString: " + idiomString);
                mIdiomTextView.setText("\"" + idiomString + "\"");
                mQuoteList.setAdapter(new ContentAdapter(mContext, resultXml));
                mIdiomTextView.setVisibility(View.VISIBLE);

                mIdiomTextView.post(new Runnable(){
                    @Override
                    public void run() {
                        mQuoteList.setVisibility(View.VISIBLE);
                    }
                });
            }

        }
    });

}

IdiomView

public class IdiomView extends TextView {

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

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.i(getClass().getSimpleName(), "onDraw(): " + this.getLineCount());
        if(this.getLineCount() > 2){
            this.setTextSize(TypedValue.COMPLEX_UNIT_PX, this.getTextSize()-1);
        }
        else{
            this.setTextColor(getResources().getColor(R.color.text));
        }
        Log.i(getClass().getSimpleName(), "onDraw(): " + this.getLineCount());

    }


}

非常感谢。

1
你可以通过使用 getPaint() 方法来避免重复设置文本大小(并同时重新绘制小部件)。使用该 Paint 在循环中测量文本的宽度(使用不同的文本大小),直到找到适合 TextView 可用的最大宽度的值(两次),然后将该值直接用于文本大小。 - user
感谢Luksprog。这就是我应该解决问题的方式。此线程提供了有关测量TextView的详细信息。 - William Carter
2个回答

3

我以前从未在View上尝试过这个方法,但我认为它可以帮助你的应用程序在绘制IdiomView后只与ListView一起工作。您可以使用监听器来实现这一点。但是,这将需要您使用代码后台方法。

IdiomView中创建一个接口,例如:

public static interface OnDrawComplete {
    public abstract void onDrawComplete();
}

private OnDrawComplete mListener;

public IdiomView(Context context) {     
    super(context);
    // this forces it to make sure it's actually an activity
    // context so you can get a listener
    if (context instanceof Activity)
        this.mListener = context;
}

现在,在你完成绘图的最后,调用它。

if (this.mListener != null)
    this.mListener.onDrawComplete();

这将调用创建对象的Activity并通知其绘制。因此,在您的Activity中,您需要实现该接口。

public void onDrawComplete() {
    // After the draw completes, it calls this callback.
    // setup the rest of your layout now
    mQuoteList = (ListView) findViewById(R.id.quote_list);
    this.populateQuoteList();
}

您可能还可以在某些其他的View事件中调用this.mListener.onDrawComplete();,但我找不到适当的事件。您提到了View.post(runnable),这可能有效,但我也从未使用过这种方式。
最大的潜在问题是我不知道IdiomView将如何接收Context或哪个Context。在这种情况下,您需要实现接口的那个Activity的上下文。
如果没有得到正确的上下文,可能需要手动实例化并将其添加到布局中才能使用此方法。
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mIdiomTextView = new mIdiomTextView(this);
    // add to the Layout manually.  
}

以下是更详细的内容

IdiomView(成语词典)

public class IdiomView extends TextView {
    public static interface OnDrawComplete {
        public abstract void onDrawComplete();
    }

    private OnDrawComplete mListener;

    public IdiomView(Context context) {     
        this(context, 0);
    }

    public IdiomView(Context context, AttributeSet attrs){
        super(context, attrs);
        if (context instanceof Activity)
            this.mListener = context;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // do your drawing code here
        // ...
        // after drawing, call the listener, which tells your
        // Activity to deal with the ListView.
        if (this.mListener != null)
            this.mListener.onDrawComplete();
    }
}

活动

public class MyActivity extends Activity implements IdiomView.OnDrawComplete {
    //...

    private IdiomView mIdiomTextView;
    private ListView mQuoteList;

    public void onDrawComplete() {
        // After the draw completes, it calls this callback.
        // setup the rest of your layout now
        mQuoteList = (ListView) findViewById(R.id.quote_list);
        this.populateQuoteList();
        // now show the ListView
        mQuoteList.setVisibility(View.Visible);
    }

    private void populateQuoteList() {
        // populate your ListView here
    }
}

这是一个非常棒的答案,完美地回答了我提出的问题。然而,在实施这个答案之后,我计时了一下我的“IdiomView”完成绘制所需的时间,几乎需要半秒钟。我提出的问题很糟糕,其他遇到同样问题的人应该看看Luksprog(上面)的评论,他的方法只需要大约50毫秒。 - William Carter
很高兴你解决了它。看看Luksprog是否会将其添加为答案,这样你就可以接受它了。 - Kirk

2
重复设置文本大小,直到找到合适的字体大小,可能非常低效,因为每次都需要重新测量和重绘TextView。相反,您可以使用其TextView.getPaint()方法仅测量和设置文本一次。您可以在循环中使用Paint提供不同的字体大小来测量文本,直到Paint.measureText()返回的值小于小部件当前宽度的两倍为止。请注意保留HTML标签。

循环直到宽度小于两倍最大宽度仍然可以跨越三行,如果更长的单词换行到第三行,那么我只是用最大高度进行测量。 - William Carter
@WilliamCarter 我忘记了这一点,你仍然可以测量宽度,但需要进行额外的检查以确定文本的一半位于何处(在单词或空格中),并从那里进行一些调整。 - user

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接