package edu.mit.mobile.android.widget; /* * Copyright (C) 2010 MIT Mobile Experience Lab * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * portions Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; import android.view.Gravity; import android.widget.TextView; /** * A modified text view that puts an outline around the text. * Supports a limited subset of what TextViews allow. * * @author Steve Pomeroy * */ public class OutlinedTextView extends TextView { public static String TAG = OutlinedTextView.class.getSimpleName(); private Layout mLayoutOutline; private Layout mLayout; private int mGravity = Gravity.LEFT; private boolean mIncludePad = true; private float mSpacingMult = 1; private float mSpacingAdd = 0; private TextPaint mTextPaint; private TextPaint mStrokePaint = new TextPaint(); private boolean mInvalidateLayout; public OutlinedTextView(Context context) { super(context); setStrokePaint(); } public OutlinedTextView(Context context, AttributeSet attrs) { super(context, attrs); setStrokePaint(); } public OutlinedTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setStrokePaint(); } @Override public void setTextAppearance(Context context, int resid) { super.setTextAppearance(context, resid); setStrokePaint(); } public void setOutlineARGB(int alpha, int red, int green, int blue){ mStrokePaint.setARGB(alpha, red, green, blue); invalidate(); } public void setOutlineColor(int color){ mStrokePaint.setColor(color); invalidate(); } public void setOutlineWidth(float width){ mStrokePaint.setStrokeWidth(width); invalidate(); } private void setStrokePaint(){ mTextPaint = getPaint(); mStrokePaint = new TextPaint(mTextPaint); mStrokePaint.setTypeface(getTypeface()); mStrokePaint.setStyle(Paint.Style.STROKE); // TODO make these properties that can be set from XML mStrokePaint.setARGB(255, 0, 0, 0); mStrokePaint.setStrokeWidth(4); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override public void setText(CharSequence text, BufferType type) { setStrokePaint(); super.setText(text, type); mInvalidateLayout = true; invalidate(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { createLayouts(left, right); } @Override public void setGravity(int gravity) { super.setGravity(gravity); // call through to the parent, as it sets defaults mGravity = super.getGravity(); mInvalidateLayout = true; } private void createLayouts(int left, int right){ if (mInvalidateLayout){ final int width = right - left - getCompoundPaddingRight() - getCompoundPaddingLeft(); Layout.Alignment alignment; switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: alignment = Layout.Alignment.ALIGN_CENTER; break; case Gravity.RIGHT: alignment = Layout.Alignment.ALIGN_OPPOSITE; break; default: alignment = Layout.Alignment.ALIGN_NORMAL; } final CharSequence text = getText(); mLayout = new StaticLayout(text, mTextPaint, width, alignment, mSpacingMult, mSpacingAdd, mIncludePad); mLayoutOutline = new StaticLayout(text, mStrokePaint, width, alignment, mSpacingMult, mSpacingAdd, mIncludePad); mInvalidateLayout = false; } } @Override public void setIncludeFontPadding(boolean includepad) { super.setIncludeFontPadding(includepad); mIncludePad = includepad; } @Override public void setLineSpacing(float add, float mult) { super.setLineSpacing(add, mult); mSpacingAdd = add; mSpacingMult = mult; } @Override protected void onDraw(Canvas canvas) { // This routine explicitly does not call super.onDraw() as trying to match // up the drawn text with the outline is too hard. if (mLayoutOutline != null){ canvas.save(); mTextPaint.setColor(getCurrentTextColor()); // basic offsets for padding canvas.translate(getCompoundPaddingLeft(), getVerticalOffset() + getExtendedPaddingTop()); mLayoutOutline.draw(canvas); mLayout.draw(canvas); canvas.restore(); } } private int getVerticalOffset() { int voffset = 0; final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final Layout l = mLayoutOutline; if (gravity != Gravity.TOP) { int boxht; boxht = getMeasuredHeight() - getExtendedPaddingTop() - getExtendedPaddingBottom(); final int textht = l.getHeight(); if (textht < boxht) { if (gravity == Gravity.BOTTOM) { voffset = boxht - textht; } else { voffset = (boxht - textht) >> 1; } } } return voffset; } }