android自定义View----文字部分渐变效果

作者: Vonelone | 来源:发表于2017-05-09 18:30 被阅读158次

    今天做的是一个简单支持文字部分渐变效果的控件,还是先放上成果:


    loading
    tab

    如上图,这个控件可以做特殊的loading动画,比如下载、上传、等,也可以用在viewpager切换时的tab,实现文字部分变色等。

    • 实现原理:

    • 拿到文字,先把它渲染在画布上,作为底色

    • 然后对画布进行矩形裁剪clipRect(),paint换一种颜色,再把文字绘制一遍,即可

    • 裁剪的尺寸是根据外部传入的progress、渐变方向、以及文字的宽高确定

      • 先铺代码:

    attrs.xml 比较简单,定义两种对比的颜色、文字大小和文字内容 :

    <declare-styleable name="ShadeTextView">
            <attr name="text" format="string"/>
            <attr name="firstColor" format="color"/>
            <attr name="secondColor" format="color"/>
            <attr name="textSize" format="dimension"/>
        </declare-styleable>
    
    • 写一个view的子类,(其实继承TextView更方便一点)

    全局变量

        private int mDirection ; //渐变的方向
    
        public static final int DIRECTION_LEFT = 0;
        public static final int DIRECTION_RIGHT = 1;
        public static final int DIRECTION_TOP = 2;
        public static final int DIRECTION_BOTTOM = 3;
    
        public void setDirection(int direction) {
            mDirection = direction;
            postInvalidate();
        }
    
        private Paint mPaint;
    
        private String mText;//显示的文字
    
        private int mTextSize;//文字大小
    
        private float mProgress;//渐变位置
    
        private int mFirstColor;//base文字颜色
    
        private int mSecondColor;//变化的文字颜色
    
        public void setmProgress(float mProgress) {
            this.mProgress = mProgress;
            postInvalidate();
        }
    

    构造器,我的千篇一律的写法~~

        public ShadeTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ShadeTextView, 0, defStyleAttr);
    
            for (int i = 0; i < typedArray.length(); i++) {
                int attr = typedArray.getIndex(i);
                switch (attr) {
                    case R.styleable.ShadeTextView_text:
                        mText = typedArray.getString(attr);
                        break;
                    case R.styleable.ShadeTextView_firstColor:
                        mFirstColor = typedArray.getColor(attr, Color.BLACK);
                        break;
                    case R.styleable.ShadeTextView_secondColor:
                        mSecondColor = typedArray.getColor(attr, Color.BLUE);
                        break;
                    case R.styleable.ShadeTextView_textSize:
                        mTextSize = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
                                TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                        break;
                    /*case R.styleable.ShadeTextView_progress:
                        mProgress = typedArray.getInteger(attr, 30);
                        break;*/
                }
            }
            typedArray.recycle();
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setTextSize(mTextSize);
        }
    

    onMeasure()的处理,如果继承TextView的话可以省略很多代码。。。

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int specMode = MeasureSpec.getMode(widthMeasureSpec);
            int specSize = MeasureSpec.getSize(widthMeasureSpec);
    
            int width = 0, height = 0;
    
            switch (specMode) {
                case MeasureSpec.EXACTLY:
                    width = specSize + getPaddingRight() + getPaddingLeft();
                    break;
    
                case MeasureSpec.AT_MOST:
                    width = (int) (mPaint.measureText(mText) + getPaddingLeft() + getPaddingRight());//先确定mPaint是否已经设置textsize
                    break;
            }
    
            specMode = MeasureSpec.getMode(heightMeasureSpec);
            specSize = MeasureSpec.getSize(heightMeasureSpec);
            switch (specMode) {
                case MeasureSpec.EXACTLY:
                    height = specSize + getPaddingTop() + getPaddingBottom();
                    break;
                case MeasureSpec.AT_MOST:
                    height = (int) (Math.abs(mPaint.getFontMetrics().bottom - mPaint.getFontMetrics().top) + getPaddingTop() + getPaddingBottom());
                    break;
            }
    
            setMeasuredDimension(width, height);
    
        }
    

    重头戏 onDraw(),本篇的思想精华所在。

        @Override
        protected void onDraw(Canvas canvas) {
            float textWidth = mPaint.measureText(mText);
            float mLeft = (getMeasuredWidth() - textWidth) / 2;
            float textHeight = Math.abs(mPaint.getFontMetrics().bottom - mPaint.getFontMetrics().top);
            float mTop = (getMeasuredHeight() - textHeight) /2 ;
    
            //先画底层的文字
            mPaint.setColor(mFirstColor);
            canvas.drawText(mText, mLeft, getY(), mPaint);
          
            mPaint.setColor(mSecondColor);
            // 接着根据传入的渐变方向、颜色等来裁剪,接着在同样的位置重新渲染文字
            if (mDirection == DIRECTION_LEFT) {
                canvas.save(Canvas.CLIP_SAVE_FLAG);
                canvas.clipRect(mLeft, 0, textWidth * mProgress + mLeft, getMeasuredHeight());
                canvas.drawText(mText, mLeft, getY(), mPaint);
                canvas.restore();
            } else if (mDirection == DIRECTION_RIGHT){
                canvas.save(Canvas.CLIP_SAVE_FLAG);
                canvas.clipRect(textWidth - textWidth * mProgress + mLeft , 0 , textWidth + mLeft, getMeasuredHeight());
                canvas.drawText(mText, + mLeft, getY(), mPaint);
                canvas.restore();
            } else if (mDirection == DIRECTION_TOP){
                canvas.save(Canvas.CLIP_SAVE_FLAG);
                canvas.clipRect(0, mTop , getWidth(), textHeight * mProgress + mTop);
                canvas.drawText(mText, + mLeft, getY(), mPaint);
                canvas.restore();
            }else if (mDirection == DIRECTION_BOTTOM){
                canvas.save(Canvas.CLIP_SAVE_FLAG);
                canvas.clipRect(0, textHeight - textHeight * mProgress + mTop, getWidth(), textHeight + mTop );
                canvas.drawText(mText, + mLeft, getY(), mPaint);
                canvas.restore();
            }
        }
        public float getY() {
            Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();
            return (getHeight() + fm.descent - fm.ascent) / 2 - fm.descent;
        }
    

    值得说明的是,
    float textHeight = Math.abs(mPaint.getFontMetrics().bottom - mPaint.getFontMetrics().top);
    return (getHeight() + fm.descent - fm.ascent) / 2 - fm.descent;
    这里借鉴了Android 自定义View-怎么绘制居中文本?的研究成果,怒学。

    • View基本写完,可以先测试了,先用seekbar测试四个方向的渐变效果,代码比较简单,只贴一段根据seekbar的progress值设置我们自定义View的渐变方向和渐变位置的代码:

     @Override
                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                    shadeTextView.setDirection(ShadeTextView.DIRECTION_TOP);
                    shadeTextView.setmProgress(progress * 1.0f / 100);
                }
    

    运行测试,基本OK


    运用到viewpager中的代码也比较简单,只贴核心调用部分,其他大家都很熟悉了。

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
           if (positionOffset > 0){
    
                ShadeTextView left = shadeTextViews.get(position);
                ShadeTextView right = shadeTextViews.get(position + 1);
    
                left.setDirection(1);
                right.setDirection(0);
    
                left.setmProgress(1-positionOffset);
                right.setmProgress(positionOffset);
          }
    }
    

    然后效果就是文首的viewpager切换效果了。

    源码点击查看

    相关文章

      网友评论

        本文标题:android自定义View----文字部分渐变效果

        本文链接:https://www.haomeiwen.com/subject/wbcitxtx.html