取自https://github.com/m5/MagicTextView,感谢m5
设置一个attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MagicTextView"> <attr name="innerShadowColor" format="color"/> <attr name="innerShadowRadius" format="float"/> <attr name="innerShadowDx" format="float"/> <attr name="innerShadowDy" format="float"/> <attr name="outerShadowColor" format="color"/> <attr name="outerShadowRadius" format="float"/> <attr name="outerShadowDx" format="float"/> <attr name="outerShadowDy" format="float"/> <attr name="typeface" format="string" /> <attr name="foreground" format="reference|color"/> <attr name="background" format="reference|color"/> <attr name="strokeWidth" format="float" /> <attr name="strokeMiter" format="float" /> <attr name="strokeColor" format="color" /> <attr name="strokeJoinStyle"> <enum name="miter" value="0" /> <enum name="bevel" value="1" /> <enum name="round" value="2" /> </attr> </declare-styleable> </resources>
import java.util.ArrayList; import java.util.WeakHashMap; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BlurMaskFilter; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Join; import android.graphics.Paint.Style; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.text.TextPaint; import android.util.AttributeSet; import android.util.Pair; import android.widget.TextView; import com.carcon.navi.uitools.ScreenMeasure; import com.carcon.ui.R; public class MagicTextView extends TextView { private ArrayList<Shadow> outerShadows; private ArrayList<Shadow> innerShadows; private WeakHashMap<String, Pair<Canvas, Bitmap>> canvasStore; private Canvas tempCanvas; private Bitmap tempBitmap; private Drawable foregroundDrawable; private float strokeWidth; private Integer strokeColor; private Join strokeJoin; private float strokeMiter; private int[] lockedCompoundPadding; private boolean frozen = false; public MagicTextView(Context context) { super(context); init(null); } public MagicTextView(Context context, AttributeSet attrs) { super(context, attrs); init(attrs); } public MagicTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(attrs); } public void init(AttributeSet attrs){ outerShadows = new ArrayList<Shadow>(); innerShadows = new ArrayList<Shadow>(); if(canvasStore == null){ canvasStore = new WeakHashMap<String, Pair<Canvas, Bitmap>>(); } if(attrs != null){ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MagicTextView); String typefaceName = a.getString( R.styleable.MagicTextView_typeface); if(typefaceName != null) { Typeface tf = Typeface.createFromAsset(getContext().getAssets(), String.format("fonts/%s.ttf", typefaceName)); setTypeface(tf); } if(a.hasValue(R.styleable.MagicTextView_foreground)){ Drawable foreground = a.getDrawable(R.styleable.MagicTextView_foreground); if(foreground != null){ this.setForegroundDrawable(foreground); }else{ this.setTextColor(a.getColor(R.styleable.MagicTextView_foreground, 0xff000000)); } } if(a.hasValue(R.styleable.MagicTextView_background)){ Drawable background = a.getDrawable(R.styleable.MagicTextView_background); if(background != null){ this.setBackgroundDrawable(background); }else{ this.setBackgroundColor(a.getColor(R.styleable.MagicTextView_background, 0xff000000)); } } if(a.hasValue(R.styleable.MagicTextView_innerShadowColor)){ this.addInnerShadow(a.getFloat(R.styleable.MagicTextView_innerShadowRadius, 0), a.getFloat(R.styleable.MagicTextView_innerShadowDx, 0), a.getFloat(R.styleable.MagicTextView_innerShadowDy, 0), a.getColor(R.styleable.MagicTextView_innerShadowColor, 0xff000000)); } if(a.hasValue(R.styleable.MagicTextView_outerShadowColor)){ this.addOuterShadow(a.getFloat(R.styleable.MagicTextView_outerShadowRadius, 0), a.getFloat(R.styleable.MagicTextView_outerShadowDx, 0), a.getFloat(R.styleable.MagicTextView_outerShadowDy, 0), a.getColor(R.styleable.MagicTextView_outerShadowColor, 0xff000000)); } if(a.hasValue(R.styleable.MagicTextView_strokeColor)){ float strokeWidth = a.getFloat(R.styleable.MagicTextView_strokeWidth, 1); int strokeColor = a.getColor(R.styleable.MagicTextView_strokeColor, 0xff000000); float strokeMiter = a.getFloat(R.styleable.MagicTextView_strokeMiter, 10); Join strokeJoin = null; switch(a.getInt(R.styleable.MagicTextView_strokeJoinStyle, 0)){ case(0): strokeJoin = Join.MITER; break; case(1): strokeJoin = Join.BEVEL; break; case(2): strokeJoin = Join.ROUND; break; } this.setStroke(strokeWidth, strokeColor, strokeJoin, strokeMiter); } } } public void setStroke(float width, int color, Join join, float miter){ strokeWidth = width; strokeColor = color; strokeJoin = join; strokeMiter = miter; } public void setStroke(float width, int color){ setStroke(width, color, Join.MITER, 10); } public void addOuterShadow(float r, float dx, float dy, int color){ if(r == 0){ r = 0.0001f; } outerShadows.add(new Shadow(r,dx,dy,color)); } public void addInnerShadow(float r, float dx, float dy, int color){ if(r == 0){ r = 0.0001f; } innerShadows.add(new Shadow(r,dx,dy,color)); } public void clearInnerShadows(){ innerShadows.clear(); } public void clearOuterShadows(){ outerShadows.clear(); } public void setForegroundDrawable(Drawable d){ this.foregroundDrawable = d; } public Drawable getForeground(){ return this.foregroundDrawable == null ? this.foregroundDrawable : new ColorDrawable(this.getCurrentTextColor()); } @Override public void onDraw(Canvas canvas){ super.onDraw(canvas); freeze(); Drawable restoreBackground = this.getBackground(); Drawable[] restoreDrawables = this.getCompoundDrawables(); int restoreColor = this.getCurrentTextColor(); this.setCompoundDrawables(null, null, null, null); for(Shadow shadow : outerShadows){ this.setShadowLayer(shadow.r, shadow.dx, shadow.dy, shadow.color); super.onDraw(canvas); } this.setShadowLayer(0,0,0,0); this.setTextColor(restoreColor); if(this.foregroundDrawable != null && this.foregroundDrawable instanceof BitmapDrawable){ generateTempCanvas(); super.onDraw(tempCanvas); Paint paint = ((BitmapDrawable) this.foregroundDrawable).getPaint(); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)); this.foregroundDrawable.setBounds(canvas.getClipBounds()); this.foregroundDrawable.draw(tempCanvas); canvas.drawBitmap(tempBitmap, 0, 0, null); tempCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); } if(strokeColor != null){ TextPaint paint = this.getPaint(); // paint.setTextAlign(Paint.Align.CENTER); paint.setStyle(Style.STROKE); paint.setStrokeJoin(strokeJoin); paint.setStrokeMiter(strokeMiter); this.setTextColor(strokeColor); paint.setStrokeWidth(strokeWidth); super.onDraw(canvas); paint.setStyle(Style.FILL); this.setTextColor(restoreColor); } if(innerShadows.size() > 0){ generateTempCanvas(); TextPaint paint = this.getPaint(); for(Shadow shadow : innerShadows){ this.setTextColor(shadow.color); super.onDraw(tempCanvas); this.setTextColor(0xFF000000); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); paint.setMaskFilter(new BlurMaskFilter(shadow.r, BlurMaskFilter.Blur.NORMAL)); tempCanvas.save(); tempCanvas.translate(shadow.dx, shadow.dy); super.onDraw(tempCanvas); tempCanvas.restore(); canvas.drawBitmap(tempBitmap, 0, 0, null); tempCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); paint.setXfermode(null); paint.setMaskFilter(null); this.setTextColor(restoreColor); this.setShadowLayer(0,0,0,0); } } if(restoreDrawables != null){ this.setCompoundDrawablesWithIntrinsicBounds(restoreDrawables[0], restoreDrawables[1], restoreDrawables[2], restoreDrawables[3]); } this.setBackgroundDrawable(restoreBackground); this.setTextColor(restoreColor); unfreeze(); } private void generateTempCanvas(){ String key = String.format("%dx%d", getWidth(), getHeight()); Pair<Canvas, Bitmap> stored = canvasStore.get(key); if(stored != null){ tempCanvas = stored.first; tempBitmap = stored.second; }else{ tempCanvas = new Canvas(); tempBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); tempCanvas.setBitmap(tempBitmap); canvasStore.put(key, new Pair<Canvas, Bitmap>(tempCanvas, tempBitmap)); } } // Keep these things locked while onDraw in processing public void freeze(){ lockedCompoundPadding = new int[]{ getCompoundPaddingLeft(), getCompoundPaddingRight(), getCompoundPaddingTop(), getCompoundPaddingBottom() }; frozen = true; } public void unfreeze(){ frozen = false; } @Override public void requestLayout(){ if(!frozen) super.requestLayout(); } @Override public void postInvalidate(){ if(!frozen) super.postInvalidate(); } @Override public void postInvalidate(int left, int top, int right, int bottom){ if(!frozen) super.postInvalidate(left, top, right, bottom); } @Override public void invalidate(){ if(!frozen) super.invalidate(); } @Override public void invalidate(Rect rect){ if(!frozen) super.invalidate(rect); } @Override public void invalidate(int l, int t, int r, int b){ if(!frozen) super.invalidate(l,t,r,b); } @Override public int getCompoundPaddingLeft(){ return !frozen ? super.getCompoundPaddingLeft() : lockedCompoundPadding[0]; } @Override public int getCompoundPaddingRight(){ return !frozen ? super.getCompoundPaddingRight() : lockedCompoundPadding[1]; } @Override public int getCompoundPaddingTop(){ return !frozen ? super.getCompoundPaddingTop() : lockedCompoundPadding[2]; } @Override public int getCompoundPaddingBottom(){ return !frozen ? super.getCompoundPaddingBottom() : lockedCompoundPadding[3]; } public static class Shadow{ float r; float dx; float dy; int color; public Shadow(float r, float dx, float dy, int color){ this.r = r; this.dx = dx; this.dy = dy; this.color = color; } } }这类中的描边是内描边,如果要外描边的话
在onDraw里面画完描边字体以后加上
paint.setStrokeWidth(0); super.onDraw(canvas);
调用方发如下:
xml:
<com.qwerjk.better_text.MagicTextView xmlns:qwerjk="http://schemas.android.com/apk/res/com.qwerjk.better_text" android:textSize="78dp" android:textColor="#ff333333" android:layout_width="fill_parent" android:layout_height="wrap_content" android:drawableLeft="@android:drawable/btn_star" android:textStyle="bold" android:padding="10dp" qwerjk:foreground="@drawable/fake_luxury_tiled" qwerjk:innerShadowDy="2" qwerjk:innerShadowColor="#FF000000" qwerjk:innerShadowRadius="1" qwerjk:outerShadowDy="3" qwerjk:outerShadowColor="#FF0088ff" qwerjk:outerShadowRadius="10" qwerjk:strokeColor="#FFff0000" qwerjk:strokeJoinStyle="miter" qwerjk:strokeWidth="5" android:text="Sample" />
view = new MagicTextView(context); view.addInnerShadow(0, -1, 0, 0xFFffffff); view.addOuterShadow(0, -1, 0, 0xff000000); view.setStroke(4, 0xFFff0000); view.setForegroundDrawable(getResources().getDrawable(R.drawable.fake_luxury_tiled);