必发365游戏官方网址自定义控件,能够伸缩的寻觅栏

博文出处:可以伸缩的搜索栏,模仿中兴应用市集,迎接大家关切自己的博客,多谢!

必发365游戏官方网址 1

多年来不短一段时间都在撸自定义View,说实话,在成为大咖的路上,这一块是必走之路,而作为菜鸡的本人不能不把它啃下来,俗话说不会自定义View的大拿不是好的程序员,所以呢,强撸吧!!!GitHub传送门:https://github.com/SuperKotlin/CirclrProgress
先来看一温智翔以妹子的图形,额呸呸呸,不对,是来看一张效果图图片(手动好笑)。

本项目标 GitHub 地址:

图1:预期效果与利益图

必发365游戏官方网址 2

至于寻觅栏,能够说各类 app
都有两样的样式。影响相比长远的就有华为应用市镇的寻觅栏(一样,简书的寻找栏也是近乎的)。

先上一张效果图,该图为自己制作而成效于展现游戏中,游戏发烧友的各样才能数值的一张雷达图。

circlrProgress.gif

而明日,正是带您来促成人中学兴应用市集那样的搜索栏。

对于自定义控件你得必要有以下文化储备:(RaderView涉及到的只是点比较基础)

观察那般的五个成效,该怎么着去贯彻啊?上边小编就一步一步的把他撸出来!

大家先放上大家兑现的效用图吧:

1.自定义属性

2.onMeasure/onDraw

思路


1.自定义属性:文字的颜色和字体大小,圆弧的颜色和宽度,一开始加载进度的位置,等等;
2.画出需要的效果:画圆弧,画字体,使用画笔paint在canvas上绘制;
3.设置进度,重新绘制;
4.接口回调。

好的,接下去大家就根据那一个思路一步一步的撸:

必发365游戏官方网址 3demo效果图

鸿洋大神有一篇对自定义View介绍的的比较详细的稿子,附链接。

1.自定义我们要求的性情:


在values文件夹下新建文件attrs.xml

    <declare-styleable name="CircleProgressView">
        <!--画笔宽度-->
        <attr name="progress_paint_width" format="dimension" />
        <!--画笔颜色-->
        <attr name="progress_paint_color" format="color" />
        <!--字体颜色-->
        <attr name="progress_text_color" format="color" />
        <!--字体尺寸-->
        <attr name="progress_text_size" format="dimension" />
        <!--加载进度的开始位置-->
        <attr name="location" format="enum">
            <enum name="left" value="1" />
            <enum name="top" value="2" />
            <enum name="right" value="3" />
            <enum name="bottom" value="4" />
        </attr>
    </declare-styleable>

接下来在自定义View中取得并设置这个属性:
率先,来声称大家的天性类型:

private int mCurrent;//当前进度
private Paint mPaintOut;
private Paint mPaintCurrent;
private Paint mPaintText;
private float mPaintWidth;//画笔宽度
private int mPaintColor = Color.RED;//画笔颜色
private int mTextColor = Color.BLACK;//字体颜色
private float mTextSize;//字体大小
private int location;//从哪个位置开始
private float startAngle;//开始角度

获得自定义属性值:

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView);
location = array.getInt(R.styleable.CircleProgressView_location, 1);
mPaintWidth = array.getDimension(R.styleable.CircleProgressView_progress_paint_width, dip2px(context, 4));//默认4dp
mPaintColor = array.getColor(R.styleable.CircleProgressView_progress_paint_color, mPaintColor);
mTextSize = array.getDimension(R.styleable.CircleProgressView_progress_text_size, dip2px(context, 18));//默认18sp
mTextColor = array.getColor(R.styleable.CircleProgressView_progress_text_color, mTextColor);
array.recycle();

初步化和赋值:

//画笔->背景圆弧
mPaintOut = new Paint();
mPaintOut.setAntiAlias(true);
mPaintOut.setStrokeWidth(mPaintWidth);
mPaintOut.setStyle(Paint.Style.STROKE);
mPaintOut.setColor(Color.GRAY);
mPaintOut.setStrokeCap(Paint.Cap.ROUND);
//画笔->进度圆弧
mPaintCurrent = new Paint();
mPaintCurrent.setAntiAlias(true);
mPaintCurrent.setStrokeWidth(mPaintWidth);
mPaintCurrent.setStyle(Paint.Style.STROKE);
mPaintCurrent.setColor(mPaintColor);
mPaintCurrent.setStrokeCap(Paint.Cap.ROUND);
//画笔->绘制字体
mPaintText = new Paint();
mPaintText.setAntiAlias(true);
mPaintText.setStyle(Paint.Style.FILL);
mPaintText.setColor(mTextColor);
mPaintText.setTextSize(mTextSize);

怎么,想不想学?

http://blog.csdn.net/lmj623565791/article/details/24252901/

2.画出要求的功效:画圆弧,画字体,使用画笔paint在canvas上制图:


留心:绘制操作是在onDraw(Canvas canvas)方法中。
率先步:绘制背景灰黄圆弧:


笔者们应用cancas的drawArc()方法,来打听一下以此法子是何许意思:

public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}

oval  // 绘制范围
startAngle  // 开始角度
sweepAngle  // 扫过角度
useCenter   // 是否使用中心

关于那么些都以怎么意思,小编讲一群未必能懂,如何做?

必发365游戏官方网址 4

举个栗子.jpg

倘若是如此:canvas.drawArc(rectF, 45, 90, false, mPaintOut);
代表:从45°开端,扫过90°范围,方向是顺时针方向绘制,至于特别false是什么意思,权且先不管,传true一般画扇形图技艺用到。来探望草图:

必发365游戏官方网址 5

one.jpg

好的,胸有定见,假使我们要画二个圆形咋做吧,极粗略,扫过的限量是360°就OK了。源点在哪个地方就无所谓了。看到此间有些人就要问了,画圆为啥不选用canvas.drawCircle()方法,那是因为前边要画的弧形使用的也是drawArc()方法,所以为了易懂大家都用那几个吧(手动大笑)。

 //绘制背景圆弧,因为画笔有一定的宽度,所有画圆弧的范围要比View本身的大小稍微小一些,不然画笔画出来的东西会显示不完整
 RectF rectF = new RectF(mPaintWidth / 2, mPaintWidth / 2, getWidth() - mPaintWidth / 2, getHeight() - mPaintWidth / 2);
 canvas.drawArc(rectF, 0, 360, false, mPaintOut);

咱俩先来简述一下降成的思路吧,其实并不复杂。


其次步:绘制当前速度的拱形

此间有人就发掘了,我们还持续遵照上边的不二法门只要传入分裂的sweepAngle扫过角度的值不就好了吗?yes,没错。大家如若总括出脚下百分比所对应的角度值是某些度就OK了。很简短的一个公式:画个草图吧!

必发365游戏官方网址 6

two.jpg

那就是说代码就很轻松写了。

//绘制当前进度
float sweepAngle = 360 * mCurrent / 100;
canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);

先是,在找寻栏还未张开时,先鲜明半径 兰德酷路泽 ,然后假诺一个变量 offset
用来动态更改寻觅栏的肥瘦。如图所示:

本文将整合上文的基础知识,举办实践性地完成一个满意特殊须要的自定义View。本文将从平常流程来编写RaderView,包涵遭受的多多标题及思维。小编将努力为你展现三个相比完好的自定义View的落实进程。

其三步:绘制文字

先来拜会canvas的点子:

// 参数分别为 (文本 基线x 基线y 画笔)
canvas.drawText(String text,  float x, float y,Paint paint);

基线是个什么样鬼,画个草图吧!

必发365游戏官方网址 7

three.jpg

简单就是文字左下角的不胜点的坐标。
而大家要把文字画在View的骨干点地点,所以起先撸啊,
来,先求x点坐标=View宽度的百分之五十减去文字宽度的五成,思虑一下是否?
y点的坐标=View中度的十分之五+文字中度的八分之四。
OK,上代码:

 //绘制进度数字
 String text = mCurrent + "%";
 //获取文字宽度
 float textWidth = mPaintText.measureText(text, 0, text.length());
 float dx = getWidth() / 2 - textWidth / 2;
 Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt();
 float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
 float baseLine = getHeight() / 2 + dy;
 canvas.drawText(text, dx, baseLine, mPaintText);

干什么如此写,说白了都以套路。。。。。。

必发365游戏官方网址 8示意图

一.自定义属性

重组大家预料的功能图,这终究一个相比简单的自定义View,图中唯有七个天性是大家须求关切的。

1.图中的文字大小

2.由基本点到某二个点的线条的尺寸

综合上述两点,在value能源文件下增进attrs.xml文件,并加多如下属性。

自定义属性的格式为:名称name,格式format(string,color,demension,integer,enum,reference,float,boolean,fraction,flag)

<resources>

    <attr name=”diameter” format=”dimension”></attr>

    <attr name=”textsize” format=”dimension”></attr>

    <declare-styleable name=”radar_view”>

        <attr name=”diameter”/>

        <attr name=”textsize”/>

    </declare-styleable>

</resources>

以上则是对质量的概念部分。在RaderView的构造方法中获得大家早就定义好的性质:

privateint diameter;

privateint textsize;

    public RadarView(Context context, AttributeSet attrs, int
defStyleAttr) {

        super(context, attrs, defStyleAttr);

        ……

        TypedArray a =
context.getTheme().obtainStyledAttributes(attrs,
R.styleable.radar_view, defStyleAttr, 0);

        int n = a.getIndexCount();

        for (int i = 0; i < n; i++) {

            int attr = a.getIndex(i);

            switch (attr) {

                case R.styleable.radar_view_diameter:

                    diameter = a.getDimensionPixelSize(attr, (int)
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,
getResources().getDisplayMetrics()));

                    break;

                case R.styleable.radar_view_textsize:

                    textsize = a.getDimensionPixelSize(attr, (int)
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,
getResources().getDisplayMetrics()));

                    break;

            }

        }……

        a.recycle();

    }

须要留神的是,TypedValue.applyDimension方法用于将sp调换px,属性a记得recycle,上述代码记得写到构造器中。

3.设置进程,重新绘制;


为了让最近进程mCurrent从0~100的加码,大家必要暴光一个主意可以实时的安装mCurrent值然后不断的开展绘图分界面。

/**
 * 设置当前进度并重新绘制界面
 *
 * @param mCurrent
 */
public void setmCurrent(int mCurrent) {
    this.mCurrent = mCurrent;
    invalidate();
}

信任很所人都知晓借使调用了
invalidate()方法,通常情状下系统就能够调用onDraw方法,然后就可以不断的绘图分界面了。
此间写个属性动画来达成进程条加载的效应:

//进度条从0到100
ValueAnimator animator = ValueAnimator.ofFloat(0, 100);
animator.setDuration(4000);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float current = (float) animation.getAnimatedValue();
        mCircleProgressView.setmCurrent((int) current);
    }
});
animator.start();

从而能够获得三个公式:offset = total width – 2 * R ;

二.控件的长度宽度衡量

望文生义,这里要促成复写onMeasure方法。先上一部分代码(这里为了节约版面,只上小幅度的度量进程)

  int widthMode = MeasureSpec.getMode(widthMeasureSpec);

  int widthSize = MeasureSpec.getSize(widthMeasureSpec);

  if (widthMode == MeasureSpec.EXACTLY) {

            if (getWidth() / 2 > centerX) {

                centerX = getWidth() / 2;

            } else {

                centerX = diameter + getPaddingLeft();

            }

        } else {

            float mWidth = diameter;

            width = desired * 2;

            centerX = width / 2;

        }

        width=width+4*textsize;

        if (width / 2 < diameter) {

            scaleX = width / 2 * 1.0f / diameter;

        }

        if (scaleX < scaleY) {

            scaleY = scaleX;

        } else {

            scaleX = scaleY;

        }

        //由于侧边text导致基本右移

        centerX = centerX + 2 * textsize;

        setMeasuredDimension(width, height);

那么地点的代码即为比较完好的,衡量宽度的代码。下边将提议多少个自己在促成进程中遇到的主题材料,以及多少个变量代表的含义。

MeasureSpec.EXACTLY:当宽度设置为准确值恐怕MATCH_PARENT

MeasureSpec.AT_MOST:当宽度设置为WRAP_CONTENT

鲁人持竿自身的怀恋以及预期的图,作者平素将width的值设置为widthSize+padding(左右),但是当绘制时,不能够将右边手和侧边的字(生存、加害)浮现完全,所以应当加上4*textsize的肥瘦,最后的结果才是那么些控件全部的升幅。这里还应该有叁个地点要小心的是,小编在此间
加上
4*textsize的做法是老大不指出的,思量到拓宽性,大家相应能够本人设置左右的书体,那么当X轴上助长的字体不为4的时候,就能并发难点。因而,在那应该加上记录字体个数的变量,举例mTextCount,最终上涨的幅度加上mTextCount
* textSize就好啦~

diamater:雷达图中央到有个别定点的边长

centerX:记录大旨点,用于从基本绘制雷达图的background,很好了解。

scaleX:缩放的百分比。在步长的衡量中,平常会遇上长和宽装置的可比不搭配(高大于宽,或索性直接设置为MATCH_PARENT),今年就要求开展缩放。能够阅览,当widthMode为MeasureSpec.EXACTLY时,表达大家为那几个控件设置了切实可行的大幅度大概为MATCH_PARENT(控件宽度实际不是diameter)。那么当diameter(半径)>width/2时,就须要展开缩放。

当程序实行到这边的时候,我发觉当自己设置width和height同时,而且都须求展开0.5倍缩放时,小编的图是如此的:

必发365游戏官方网址 9

图2

为什么会见世这种景象吗?答案是Y轴上必要*根号3/2(那是干什么吗??嘿嘿嘿)

实际上这种情景频频要出新在急需缩放的情形下,只是陈设开展到缩放这一步,会冒出这几个主题材料,原因便是纵轴上的冲天,并非我们设置的diameter,因为diameter被掰弯了。。。由此这些缩放比例很有要求。

假若将图片展现完全,那么就供给取scaleX和scaleY中非常小得值。

从这之后,onMeasure中的方法介绍完成。

4.接口回调。


这一步就很轻松了,只要监听进程条达到100便是瓜熟蒂落了加载,所以大家先来写一个接口。

//声明接口
public interface OnLoadingCompleteListener {
        void complete();
}
//暴露回调方法
 public void setOnLoadingCompleteListener(OnLoadingCompleteListener loadingCompleteListener) {
   this.mLoadingCompleteListener = loadingCompleteListener;
 }
//监听
  if (mLoadingCompleteListener != null && mCurrent == 100) {
       mLoadingCompleteListener.complete();
   }

在相应的Activity中回调接口就足以了。

OK,到那边便是全数达成了,上一张全家福吧:

那正是说鲜明,offset 的取值就在 [0, total width – 2 * R] 之间了。

三:绘制

前方介绍的无数变量都认为着给绘制打基础。即onDraw函数的落实

观望预期效果与利益图中有三有的需求绘制

1.背景

2.晶莹剔透的雷达填充部分

3.文字

先是背景的绘图,首假使骨架部分的绘图,那有个别能够依靠本身的须要绘制。未有困难,独一相比较复杂的便是总计增加交点的比例,和制图的过程。

Hexagon记录了6个点的坐标,通过draw6point方法将其遵照相邻顺序连线。

private void initBackground(Canvas canvas) {

        canvas.scale(scaleX, scaleY);

        for (int i = 1; i <= 4; i++) {

            dia = diameter / 4 * i;

            sqrt3diameter = (int) (Math.sqrt(3) * dia / 2);

            halfDiameter = dia / 2;

            Hexagon hexagon = new Hexagon(centerX + halfDiameter,
centerY + sqrt3diameter, centerX + dia, centerY, centerX +
halfDiameter, centerY – sqrt3diameter,

                    centerX – halfDiameter, centerY – sqrt3diameter,
centerX – dia, centerY, centerX – halfDiameter, centerY +
sqrt3diameter);

            draw6point(canvas, hexagon);

        }

    }

此地作者的绘图方法是从中式茶食到外边逐圈绘制。

半晶莹剔透的雷达填充部分艺术如上,只是hexagon类中寄放的是技巧值终点的值。

文字更简便易行啦,调用canvas的drawText方法就能够了。

2,3局地的绘图请各位进行贰个简便的企图。

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    tools:context="com.zhuyong.circleprogress.MainActivity">

    <com.zhuyong.circleprogress.CircleProgressView
        android:id="@+id/circle_view"
        android:layout_width="150dp"
        android:layout_height="150dp"
        app:location="left"
        app:progress_paint_color="#FF4081"
        app:progress_paint_width="4dp"
        app:progress_text_color="#3F51B5"
        app:progress_text_size="16sp" />

</LinearLayout>

从而,我们能够借助属性动画来成功那数值的成形。在调用 invalidate()
举行重绘,达到动态扩展搜索栏宽度的作用。反之,关闭寻觅栏也是同理的。

四.动画功能

属性动画部分的有关主题素材请参见郭林写的

Android属性动画完全分析

动画效果则是用属性动画达成的三个特别轻易的匀速扩散的效应。Evaluator如下

public class RadarEvaluator implements TypeEvaluator<Hexagon> {

    public Hexagon evaluate(float fraction, Hexagon startValue,
Hexagon endValue) {

        int x1 = (int) (startValue.getX1() + fraction *
(endValue.getX1() – startValue.getX1()));

        int y1 = (int) (startValue.getY1() + fraction *
(endValue.getY1() – startValue.getY1()));

        ……

        return new Hexagon(x1, y1, x2, y2, x3, y3, x4, y4, x5, y5, x6,
y6);

    }

}

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    private CircleProgressView mCircleProgressView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mCircleProgressView = (CircleProgressView) findViewById(R.id.circle_view);

        //进度条从0到100
        ValueAnimator animator = ValueAnimator.ofFloat(0, 100);
        animator.setDuration(4000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float current = (float) animation.getAnimatedValue();
                mCircleProgressView.setmCurrent((int) current);
            }
        });
        animator.start();

        mCircleProgressView.setOnLoadingCompleteListener(new CircleProgressView.OnLoadingCompleteListener() {
            @Override
            public void complete() {
                Toast.makeText(MainActivity.this, "加载完成", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

那么上边就用代码来完毕它咯!

五.总结

自身做完这些控件后,对自定义View中onMeasure和onDraw部分有了迟早理解。个人以为在图纸的形制部分,算是比较轻便的,独一大概辛劳的应当是长宽的总计和图像的绘图。

写完这些控件后,笔者认为自个儿只是知道了皮毛。

学无边无际,共勉。假设有哪些地点有标题、也许能够革新,希望得以共同研究。

CircleProgressView.java:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by zhuyong on 2017/6/22.
 */

public class CircleProgressView extends View {

    private int mCurrent;//当前进度
    private Paint mPaintOut;
    private Paint mPaintCurrent;
    private Paint mPaintText;
    private float mPaintWidth;//画笔宽度
    private int mPaintColor = Color.RED;//画笔颜色
    private int mTextColor = Color.BLACK;//字体颜色
    private float mTextSize;//字体大小
    private int location;//从哪个位置开始
    private float startAngle;//开始角度

    private OnLoadingCompleteListener mLoadingCompleteListener;

    public CircleProgressView(Context context) {
        this(context, null);
    }

    public CircleProgressView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView);
        location = array.getInt(R.styleable.CircleProgressView_location, 1);
        mPaintWidth = array.getDimension(R.styleable.CircleProgressView_progress_paint_width, dip2px(context, 4));//默认4dp
        mPaintColor = array.getColor(R.styleable.CircleProgressView_progress_paint_color, mPaintColor);
        mTextSize = array.getDimension(R.styleable.CircleProgressView_progress_text_size, dip2px(context, 18));//默认18sp
        mTextColor = array.getColor(R.styleable.CircleProgressView_progress_text_color, mTextColor);
        array.recycle();

        //画笔->背景圆弧
        mPaintOut = new Paint();
        mPaintOut.setAntiAlias(true);
        mPaintOut.setStrokeWidth(mPaintWidth);
        mPaintOut.setStyle(Paint.Style.STROKE);
        mPaintOut.setColor(Color.GRAY);
        mPaintOut.setStrokeCap(Paint.Cap.ROUND);
        //画笔->进度圆弧
        mPaintCurrent = new Paint();
        mPaintCurrent.setAntiAlias(true);
        mPaintCurrent.setStrokeWidth(mPaintWidth);
        mPaintCurrent.setStyle(Paint.Style.STROKE);
        mPaintCurrent.setColor(mPaintColor);
        mPaintCurrent.setStrokeCap(Paint.Cap.ROUND);
        //画笔->绘制字体
        mPaintText = new Paint();
        mPaintText.setAntiAlias(true);
        mPaintText.setStyle(Paint.Style.FILL);
        mPaintText.setColor(mTextColor);
        mPaintText.setTextSize(mTextSize);

        if (location == 1) {//默认从左侧开始
            startAngle = -180;
        } else if (location == 2) {
            startAngle = -90;
        } else if (location == 3) {
            startAngle = 0;
        } else if (location == 4) {
            startAngle = 90;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int size = width > height ? height : width;
        setMeasuredDimension(size, size);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制背景圆弧,因为画笔有一定的宽度,所有画圆弧的范围要比View本身的大小稍微小一些,不然画笔画出来的东西会显示不完整
        RectF rectF = new RectF(mPaintWidth / 2, mPaintWidth / 2, getWidth() - mPaintWidth / 2, getHeight() - mPaintWidth / 2);
        canvas.drawArc(rectF, 0, 360, false, mPaintOut);

        //绘制当前进度
        float sweepAngle = 360 * mCurrent / 100;
        canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);

        //绘制进度数字
        String text = mCurrent + "%";
        //获取文字宽度
        float textWidth = mPaintText.measureText(text, 0, text.length());
        float dx = getWidth() / 2 - textWidth / 2;
        Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt();
        float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
        float baseLine = getHeight() / 2 + dy;
        canvas.drawText(text, dx, baseLine, mPaintText);

        if (mLoadingCompleteListener != null && mCurrent == 100) {
            mLoadingCompleteListener.complete();
        }
    }

    /**
     * 获取当前进度值
     *
     * @return
     */
    public int getmCurrent() {
        return mCurrent;
    }

    /**
     * 设置当前进度并重新绘制界面
     *
     * @param mCurrent
     */
    public void setmCurrent(int mCurrent) {
        this.mCurrent = mCurrent;
        invalidate();
    }

    public void setOnLoadingCompleteListener(OnLoadingCompleteListener loadingCompleteListener) {
        this.mLoadingCompleteListener = loadingCompleteListener;
    }

    public interface OnLoadingCompleteListener {
        void complete();
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}

GitHub传送门:源码

关于自定义的属性,大家能够想到的有搜索栏的背景颜色、寻找栏的岗位、搜索栏的意况等。具体的能够查阅下边包车型大巴attrs.xml 。依照罗马尼亚语应该能驾驭对应属性的法力了。

<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="SearchBarView"> <attr name="search_bar_color" format="color|reference" /> <attr name="search_bar_position" format="enum"> <enum name="position_left" value="4" /> <enum name="position_right" value="1" /> </attr> <attr name="search_bar_status" format="enum"> <enum name="status_close" value="4" /> <enum name="status_open" value="1" /> </attr> <attr name="search_bar_duration" format="integer" /> <attr name="search_bar_hint_text" format="string|reference" /> <attr name="search_bar_icon" format="reference" /> <attr name="search_bar_hint_text_color" format="color|reference" /> <attr name="search_bar_hint_text_size" format="dimension|reference" /> </declare-styleable></resources>

而在构造器中,确定正是初步化一些 attrs
中的全局变量了,那亦非根本,都以机械式的代码。

public SearchBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SearchBarView); searchBarColor = array.getColor(R.styleable.SearchBarView_search_bar_color, DEFAULT_SEARCH_BAR_COLOR); mPosition = array.getInteger(R.styleable.SearchBarView_search_bar_position, DEFAULT_RIGHT_POSITION); mStatus = array.getInteger(R.styleable.SearchBarView_search_bar_status, STATUS_CLOSE); int mDuration = array.getInteger(R.styleable.SearchBarView_search_bar_duration, DEFAULT_ANIMATION_DURATION); int searchBarIcon = array.getResourceId(R.styleable.SearchBarView_search_bar_icon, android.R.drawable.ic_search_category_default); mSearchText = array.getText(R.styleable.SearchBarView_search_bar_hint_text); searchTextColor = array.getColor(R.styleable.SearchBarView_search_bar_hint_text_color, DEFAULT_SEARCH_TEXT_COLOR); float defaultTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, DEFAULT_HINT_TEXT_SIZE, getResources().getDisplayMetrics; float searchTextSize = array.getDimension(R.styleable.SearchBarView_search_bar_hint_text_size, defaultTextSize); defaultHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_HEIGHT, getResources().getDisplayMetrics; array.recycle(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(searchBarColor); mPaint.setTextSize(searchTextSize); mRectF = new RectF(); mDstRectF = new RectF(); bitmap = BitmapFactory.decodeResource(getResources(), searchBarIcon); initAnimator(mDuration);}

initAnimator 方法中是三个天性动画,张开和破产动画。特别 easy 的代码。

private void initAnimator(long duration) { AccelerateInterpolator accelerateInterpolator = new AccelerateInterpolator(); ValueAnimator.AnimatorUpdateListener animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mOffsetX =  animation.getAnimatedValue(); invalidate(); } }; // init open animator openAnimator = new ValueAnimator(); openAnimator.setInterpolator(accelerateInterpolator); openAnimator.setDuration; openAnimator.addUpdateListener(animatorUpdateListener); openAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { mStatus = STATUS_PROCESS; } @Override public void onAnimationEnd(Animator animation) { mStatus = STATUS_OPEN; invalidate; // init close animator closeAnimator = new ValueAnimator(); openAnimator.setInterpolator(accelerateInterpolator); closeAnimator.setDuration; closeAnimator.addUpdateListener(animatorUpdateListener); closeAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { mStatus = STATUS_PROCESS; } @Override public void onAnimationEnd(Animator animation) { mStatus = STATUS_CLOSE; } });}

同样,onMeasure 中的代码也是很机械的,基本上都以同两个套路了。

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { mWidth = widthSize; } else { mWidth = widthSize; } if (heightMode == MeasureSpec.EXACTLY) { mHeight = heightSize; } else { mHeight =  defaultHeight; if (heightMode == MeasureSpec.AT_MOST) { mHeight = Math.min(heightSize, mHeight); } } // 搜索栏小圆圈的半径 mRadius = Math.min(mWidth, mHeight) / 2; if (mStatus == STATUS_OPEN) { mOffsetX = mWidth - mRadius * 2; } setMeasuredDimension(mWidth, mHeight);}

onDraw
中先画了找寻栏的背景,然后是找寻栏的Logo,最终是寻觅栏的唤醒文字。

画背景的时候,是索要基于寻觅栏在左侧依旧右侧的职位来鲜明值的。

而画图标的时候,是依附寻找栏关闭时那二个圆的内切星型作为 Rect 的。

谈起底画提醒文字没什么好讲的了,都以定死的代码。

@Overrideprotected void onDraw(Canvas canvas) { // draw search bar mPaint.setColor(searchBarColor); int left = mPosition == DEFAULT_RIGHT_POSITION ? mWidth - 2 * mRadius - mOffsetX : 0; int right = mPosition == DEFAULT_RIGHT_POSITION ? mWidth : 2 * mRadius + mOffsetX; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { canvas.drawRoundRect(left, 0, right, mHeight, mRadius, mRadius, mPaint); } else { mRectF.set(left, 0, right, mHeight); canvas.drawRoundRect(mRectF, mRadius, mRadius, mPaint); } // draw search bar icon mDstRectF.set(left +  ((1 - Math.sqrt * mRadius),  ((1 - Math.sqrt * mRadius), left +  ((1 + Math.sqrt * mRadius),  ((1 + Math.sqrt * mRadius)); canvas.drawBitmap(bitmap, null, mDstRectF, mPaint); // draw search bar text if (mStatus == STATUS_OPEN && !TextUtils.isEmpty(mSearchText)) { mPaint.setColor(searchTextColor); Paint.FontMetrics fm = mPaint.getFontMetrics(); double textHeight = Math.ceil(fm.descent - fm.ascent); canvas.drawText(mSearchText.toString(), 2 * mRadius,  (mRadius + textHeight / 2 - fm.descent), mPaint); }}

最后,需要将 startOpenstartClose
方法暴光给外界,方便调用。在当中间就是调用三个属性动画而已。

/** * 判断搜索栏是否为打开状态 * * @return */public boolean isOpen() { return mStatus == STATUS_OPEN;}/** * 判断搜索栏是否为关闭状态 * * @return */public boolean isClose() { return mStatus == STATUS_CLOSE;}/** * 打开搜索栏 */public void startOpen() { if  { return; } else if (openAnimator.isStarted { return; } else if (closeAnimator.isStarted { closeAnimator.cancel(); } openAnimator.setIntValues(mOffsetX, mWidth - mRadius * 2); openAnimator.start();}/** * 关闭搜索栏 */public void startClose() { if ) { return; } else if (closeAnimator.isStarted { return; } else if (openAnimator.isStarted { openAnimator.cancel(); } closeAnimator.setIntValues(mOffsetX, 0); closeAnimator.start();}

到那也大半了,该讲的都讲了,那篇写得真 TMD 简洁。至于和 AppBarLayout
的滥竽充数使用,见 GitHub 中的代码就可以。

有失常态的能够在上面留言。没难题的老铁可以来一波 star 。

FlexibleSearchBar:

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website