William Carter
William Carter

Reputation: 1305

How do I draw certain views after others?

I have a simple LinearLayout containing only a custom view extending TextView (which I shall start calling "IdiomView") and a ListView. The only difference in IdiomView from a normal TextView is that I have overridden the onDraw() method to iteratively reduce the text size until the text is less than 3 lines long. My problem is that when the views are drawn, the user will see this:

 ______________
|__ACTION_BAR__|
|  IdiomView   |
|______________|
|              |
|   ListView   |
|              |
|              |
|______________|

which quickly becomes:

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

i.e. the ListView is drawn and then jumps up after IdiomView has sorted out its size.

What I would like is a way to wait until my IdiomView is fully drawn, before drawing the ListView. This post What event is fired after all views are fully drawn? explains how to line up a thread after the drawing is complete by calling View.post(Runnable). The problem is my overridden onDraw() method calls onDraw() multiple times in order to calculate the whether the smaller text covers less than 3 lines, so this element probably "finishes drawing" numerous times before I would want the ListView to appear.

I appreciate all comments and answers. Here's my current code:

layout 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>

Activity:

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

    }


}

Thank you very much.

Upvotes: 3

Views: 868

Answers (2)

user
user

Reputation: 87064

Setting the text size repeatedly until you find the proper font size could be very inefficient because your TextView will need to be remeasured and redrawn each time. Instead of doing this, you could measure and set the text only once by using its TextView.getPaint() method. You could than measure the text with the Paint in a loop providing it different font sizes until the value returned by Paint.measureText() is below two times the current width of the widget.

Upvotes: 2

Kirk
Kirk

Reputation: 16265

I've never tried this on a View before, but I think it could help direct your app to only work with the ListView, after the IdiomView is drawn. You may be able to use a listener to get this to happen. However, it will require you to use a code behind method.

Create an interface in IdiomView such as

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

Now at the end of your drawing, call it

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

This will call the Activity that created the object and notify of it's drawing. So within your Activity, you implement the interface

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

You may also be able to call this.mListener.onDrawComplete(); in some sort of other View event, but I can't find one that is appropriate. You mention the View.post(runnable), which may work but I've never used that sort of thing either.

The biggest potential issue is that I don't know how the IdiomView will receive a Context or which one. In this case you need the Activity context of the one implementing the interface.

It may be that you need to instantiate and add it to the layout manually to use this method if it doesn't get the right context.

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

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

Here is more of it fleshed out

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

Activity

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

Upvotes: 3

Related Questions