自定义视图中的不需要的填充在某些设备上使用AutoresizeTextView

时间:2013-02-07 10:52:43

标签: android measurement

我在这里解决了我以前的问题:

Can not set gravity with setGravity vertically programatically. Is this an android bug or feature?

基本上我有一个自定义Button,里面有AutoResizeTextView。我想动画按钮并垂直居中文本视图。我通过测量和使用布局和onLayout方法解决了它。它在大多数设备上都能正常工作。

但是在某些设备上,在AutoResizeTextView内部的文本上方有一个额外的填充,无论如何我无法删除。设备表现不佳的是Kindle Fire和三星Galaxy Tab2 7. Nook Color,摩托罗拉Xoom和我的摩托罗拉Defy(CyanogenMod 10)上的布局正常。

我创建了一个示例项目来演示问题。它包括3个类和一个布局文件:

activity_main.xml中:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<herrbert74.artvsample.MyButton
    android:id="@+id/textView1"
    android:layout_width="240dp"
    android:layout_height="60dp"
    android:background="@android:color/black"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:text="@string/hello_world" />

<Button
    android:id="@+id/button1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_above="@+id/textView1"
    android:layout_alignRight="@+id/textView1"
    android:layout_marginBottom="46dp"
    android:layout_marginRight="54dp"
    android:text="Button" />


</RelativeLayout>

修改了AutoResizeTextView.java:

package herrbert74.artvsample;

import android.content.Context;
import android.graphics.Canvas;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

/**
* Text view that auto adjusts text size to fit within the view. If the text
* size equals the minimum text size and still does not fit, append with an
* ellipsis.
* 
* @author Chase Colburn
* @since Apr 4, 2011
*/
public class AutoResizeTextView extends TextView {

// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 10;

// Interface for resize notifications
public interface OnTextResizeListener {
    public void onTextResize(TextView textView, float oldSize, float newSize);
}

// Off screen canvas for text size rendering
private static final Canvas sTextResizeCanvas = new Canvas();

// Our ellipse string
private static final String mEllipsis = "...";

// Registered resize listener
private OnTextResizeListener mTextResizeListener;

// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;

// Text size that is set from code. This acts as a starting point for
// resizing
private float mTextSize;

// Temporary upper bounds on the starting text size
private float mMaxTextSize = 0;

// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;

// Text view line spacing multiplier
private float mSpacingMult = 1.0f;

// Text view additional line spacing
private float mSpacingAdd = 0.0f;

// Add ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;

// Default constructor override
public AutoResizeTextView(Context context) {
    this(context, null);
}

// Default constructor when inflating from XML file
public AutoResizeTextView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

// Default constructor override
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    mTextSize = getTextSize();
}

/**
 * When text changes, set the force resize flag to true and reset the text
 * size.
 */
@Override
protected void onTextChanged(final CharSequence text, final int start,
        final int before, final int after) {
    mNeedsResize = true;
    // Since this view may be reused, it is good to reset the text size
    resetTextSize();
}

/**
 * If the text view size changed, set the force resize flag to true
 */
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    if (w != oldw || h != oldh) {
        mNeedsResize = true;
    }
}

/**
 * Register listener to receive resize notifications
 * 
 * @param listener
 */
public void setOnResizeListener(OnTextResizeListener listener) {
    mTextResizeListener = listener;
}

/**
 * Override the set text size to update our internal reference values
 */
@Override
public void setTextSize(float size) {
    super.setTextSize(size);
    mTextSize = getTextSize();

}

/**
 * Override the set text size to update our internal reference values
 */
@Override
public void setTextSize(int unit, float size) {
    super.setTextSize(unit, size);
    mTextSize = getTextSize();

}

/**
 * Override the set line spacing to update our internal reference values
 */
@Override
public void setLineSpacing(float add, float mult) {
    super.setLineSpacing(add, mult);
    mSpacingMult = mult;
    mSpacingAdd = add;
}

/**
 * Set the upper text size limit and invalidate the view
 * 
 * @param maxTextSize
 */
public void setMaxTextSize(float maxTextSize) {
    mMaxTextSize = maxTextSize;
    requestLayout();
    invalidate();
}

/**
 * Return upper text size limit
 * 
 * @return
 */
public float getMaxTextSize() {
    return mMaxTextSize;
}

/**
 * Set the lower text size limit and invalidate the view
 * 
 * @param minTextSize
 */
public void setMinTextSize(float minTextSize) {
    mMinTextSize = minTextSize;
    requestLayout();
    invalidate();
}

/**
 * Return lower text size limit
 * 
 * @return
 */
public float getMinTextSize() {
    return mMinTextSize;
}

/**
 * Set flag to add ellipsis to text that overflows at the smallest text size
 * 
 * @param addEllipsis
 */
public void setAddEllipsis(boolean addEllipsis) {
    mAddEllipsis = addEllipsis;
}

/**
 * Return flag to add ellipsis to text that overflows at the smallest text
 * size
 * 
 * @return
 */
public boolean getAddEllipsis() {
    return mAddEllipsis;
}

/**
 * Reset the text to the original size
 */
public void resetTextSize() {
    //ZBertalan:changed to mMaxTextSize
    super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaxTextSize);
    //ZBertalan:swapped them
    //mMaxTextSize = mTextSize;
    mTextSize = mMaxTextSize;
}

/**
 * Resize text after measuring ZBertalan: I commented this out because I
 * need to resize the text in onMeasure, to layout the textview based on
 * it's size
 */
/*
 * @Override protected void onLayout(boolean changed, int left, int top, int
 * right, int bottom) { if(changed || mNeedsResize) { int widthLimit =
 * (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
 * int heightLimit = (bottom - top) - getCompoundPaddingBottom() -
 * getCompoundPaddingTop(); //og.d("AnswerView", "inside ARTV.onLayout() " +
 * ); resizeText(widthLimit, heightLimit); } super.onLayout(changed, left,
 * top, right, bottom); }
 */

/**
 * @author zsbertalan Moved some code here to resize the textview here and
 *         position it based on it's new size
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int newWidth = MeasureSpec.getSize(widthMeasureSpec);
    int newHeight = MeasureSpec.getSize(heightMeasureSpec);

    resizeText(newWidth, newHeight);

    TextPaint p = getPaint();
    newHeight = getTextHeight(getText(), p, newWidth, getTextSize());

    this.setMeasuredDimension(newWidth, newHeight);
}

/**
 * Resize the text size with default width and height
 */
public void resizeText() {
    int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
    int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
    resizeText(widthLimit, heightLimit);
}

/**
 * Resize the text size with specified width and height
 * 
 * @param width
 * @param height
 */
public void resizeText(int width, int height) {
    CharSequence text = getText();
    // Do not resize if the view does not have dimensions or there is no
    // text
    if (text == null || text.length() == 0 || height <= 0 || width <= 0
            || mTextSize == 0) {
        return;
    }

    // Get the text view's paint object
    TextPaint textPaint = getPaint();

    // Store the current text size
    float oldTextSize = textPaint.getTextSize();
    // If there is a max text size set, use the lesser of that and the
    // default text size
    //ZBertalan: Bollocks. I changed to use mMaxTextSize all the time
    //float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
    float targetTextSize = mMaxTextSize;

    // Get the required text height
    int textHeight = getTextHeight(text, textPaint, width, targetTextSize);

    // Until we either fit within our text view or we had reached our min
    // text size, incrementally try smaller sizes
    while (textHeight > height && targetTextSize > mMinTextSize) {
        targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
        textHeight = getTextHeight(text, textPaint, width, targetTextSize);
    }

    // If we had reached our minimum text size and still don't fit, append
    // an ellipsis
    if (mAddEllipsis && targetTextSize == mMinTextSize
            && textHeight > height) {
        // Draw using a static layout
        TextPaint paint = new TextPaint(textPaint);
        StaticLayout layout = new StaticLayout(text, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
        layout.draw(sTextResizeCanvas);
        int lastLine = layout.getLineForVertical(height) - 1;
        int start = layout.getLineStart(lastLine);
        int end = layout.getLineEnd(lastLine);
        float lineWidth = layout.getLineWidth(lastLine);
        float ellipseWidth = paint.measureText(mEllipsis);

        // Trim characters off until we have enough room to draw the
        // ellipsis
        while (width < lineWidth + ellipseWidth) {
            lineWidth = textPaint.measureText(text.subSequence(start,
                    --end + 1).toString());
        }
        setText(text.subSequence(0, end) + mEllipsis);

    }

    // Some devices try to auto adjust line spacing, so force default line
    // spacing
    // and invalidate the layout as a side effect
    // textPaint.setTextSize(targetTextSize);
    setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
    setLineSpacing(mSpacingAdd, mSpacingMult);

    // Notify the listener if registered
    if (mTextResizeListener != null) {
        mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
    }

    // Reset force resize flag
    mNeedsResize = false;
}

// Set the text size of the text paint object and use a static layout to
// render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint originalPaint, int width,
        float textSize) {
    TextPaint paint = new TextPaint(originalPaint);
    // Update the text paint object
    paint.setTextSize(textSize);
    // Draw using a static layout
    StaticLayout layout = new StaticLayout(source, paint, width,
            Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
    layout.draw(sTextResizeCanvas);
    return layout.getHeight();
}

}

MyButton.java:     包herrbert74.artvsample;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import android.widget.LinearLayout;

public class MyButton extends LinearLayout {
private AutoResizeTextView tv;
Context ctx;

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    AutoResizeTextView tv = (AutoResizeTextView) getChildAt(0);
    int padding = getHeight() / 8;
    int left = padding;
    int top = (getHeight() - tv.getMeasuredHeight()) / 2;
    int right = padding + tv.getMeasuredWidth();
    int bottom = getHeight() / 2 + tv.getMeasuredHeight() / 2;
    tv.layout(left, top, right, bottom);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int w = MeasureSpec.getSize(widthMeasureSpec);
    int h = MeasureSpec.getSize(heightMeasureSpec);

    if (w == 0) {
        WindowManager wm = (WindowManager) ctx
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics metrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(metrics);
        w = metrics.widthPixels;
        h = metrics.heightPixels;
    }
    tv.measure(MeasureSpec.makeMeasureSpec(w - 40, MeasureSpec.AT_MOST),
            MeasureSpec.makeMeasureSpec(h - 20, MeasureSpec.AT_MOST));
    setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
            MeasureSpec.getSize(heightMeasureSpec));
}

public MyButton(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
    tv = new AutoResizeTextView(context);
    tv.setMaxTextSize(54);
    tv.setTextColor(context.getResources().getColor(android.R.color.white));
    tv.setClickable(true);
    tv.setBackgroundResource(android.R.color.darker_gray);
    addView(tv);
}

public MyButton(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs);
    ctx = context;
}

protected MyButton(Context context) {
    super(context, null);

}

public void setText(String text) {
    tv.setText(text);
}

public String getText() {
    return tv.getText().toString();
}

public void setTextSize(float f) {
    tv.setTextSize(f);
}
}

MainActivity.java:     包herrbert74.artvsample;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {
int x;
MyButton tv;

@Override
protected void onCreate(Bundle savedInstanceState) {
    x = -1;
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final String[] texts = { "normal text", "a bit longer text", "even longer text by a word", "Long. Longest text ever. Long. Long." };
    tv = (MyButton) findViewById(R.id.textView1);
    Button b = (Button) findViewById(R.id.button1);
    b.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            if (x < texts.length - 1)
                x++;
            else
                x = 0;
            tv.setText(texts[x]);
            Log.d("AnswerView", "Text: " + texts[x]);
        }

    });
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.activity_main, menu);
    return true;
}

}

在示例项目中,有一个用于更改文本的按钮。起初没有文字,然后文本越来越长。你可以看到图片上的差异。

按钮背景为黑色,文本视图背景为灰色。

以下是带有正确按钮的Defy文本视图:

Defy without text First text Second text Third text Fourth text

请注意,在Kindle上,textview已声称某个地方没有文字! Kindle Fire上的布局错误:

enter image description here First text Second text Third text Fourth text

在myButton构造函数中,我尝试了以下无效:

tv.setPadding(0, 0, 0, 0);
tv.setLineSpacing(0f, 1f);
tv.setCompoundDrawablePadding(0);
tv.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
tv.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);

0 个答案:

没有答案