自定義View--自定義輸入框

前言

無論做什么應(yīng)用,總是有需要輸入框的地方蜒什,總的來說,輸入框都不是單獨(dú)存在的疤估,一般會(huì)需要標(biāo)題灾常、輸入框(一般需要自定義邊框/下劃線/光標(biāo)顏色長度等)、左右圖標(biāo)铃拇,密碼類型的輸入框還可能需要密碼可見按鈕钞瀑,如果自己寫布局,布局文件會(huì)比較臃腫慷荔,使用也不方便(因?yàn)槟阈枰獑为?dú)控制text雕什、hint、icon、title等等對應(yīng)的view)贷岸。為了解決這個(gè)問題壹士,于是想到寫一個(gè)輸入框控件

先附上github地址,不想看我多bb的直接用起來就是
https://github.com/NotSeriousCoder/FormInputView

控件長這樣


自定義輸入框

思路

輸入框類型

一般在表單里面偿警,用到的輸入框類型如下:

  1. 普通文本躏救,啥都能輸入
  2. 單行文本框
  3. 數(shù)字文本框
  4. 密碼輸入框(文本/純數(shù)字)
  5. 點(diǎn)擊選擇框(即無法手動(dòng)輸入,一般是收到回調(diào)之后彈一個(gè)窗口螟蒸,用戶選擇)

布局

想清楚類型落剪,就可以開始思考布局(就是上面效果圖那個(gè)樣子啦)∧蚵總的來說,最外層一個(gè)FrameLayout呢堰,最外層是邊框抄瑟,里面一個(gè)LinearLayout用來放圖標(biāo)和輸入框。其他都好說枉疼,關(guān)鍵的地方皮假,在于邊框的繪制,由于要做成顏色可變骂维,而且要要給title的位置留白惹资,那么就不能使用固定圖片來做background,要手動(dòng)繪制航闺。

實(shí)現(xiàn)

1.需要仿寫一部分EditText的常用方法(因?yàn)椴皇侵苯永^承EditText)褪测,比如setText(text)、setHint(hint)之類潦刃,另外要提供getEditText()
2.通過onMeasure()計(jì)算內(nèi)部控件的寬度(要留出左右邊框的位置)侮措,高度則保持自適應(yīng)即可(上下邊框位置由padding控制)
3.繪制邊框及title
4.控制內(nèi)部子控件的擺放位置,防止內(nèi)部子控件放置在視野之外

具體實(shí)現(xiàn)

這里只講重點(diǎn)乖杠,也就是視圖的測量分扎、布局、繪制胧洒,其他都是些簡單的內(nèi)容畏吓,不贅述。
View的生成流程卫漫,我們可利用的菲饼,就是
測量--onMeasure(int widthMeasureSpec, int heightMeasureSpec)
布局--onLayout(boolean changed, int left, int top, int right, int bottom)
繪制--onDraw(Canvas canvas)

想深入學(xué)習(xí),可以看這篇文章(郭霖大神列赎,想必學(xué)Android的都不陌生吧)
Android LayoutInflater原理分析巴粪,帶你一步步深入了解View

測量--onMeasure

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

        //子控件的寬度=父控件的寬度-2*邊框筆觸寬度
        //這樣就不會(huì)踩線
        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                (int) (MeasureSpec.getSize(widthMeasureSpec) - 2.2 * Math.max(borderWidth, radius)),
                MeasureSpec.EXACTLY
        );
        //給每個(gè)子控件設(shè)置上
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.measure(
                    childWidthMeasureSpec,
                    heightMeasureSpec
            );
        }
        setupIconSize();
    }

首先,是設(shè)置上下Padding,給上下邊框+title留出位置肛根,因?yàn)闆]有去細(xì)究padding的設(shè)置是否會(huì)對子控件的測量產(chǎn)生影響(其實(shí)我覺得會(huì))辫塌,所以保險(xiǎn)起見,我選擇在測量前先設(shè)置
padding存在兩種情況
??1.文字高度>邊框筆觸寬度
??2.文字高度<邊框筆觸寬度
??取較大值為topPadding派哲, bottomPadding則固定是邊框的筆觸寬度即可
然后設(shè)置子控件寬度臼氨,沒啥好講,看代碼即可

布局--onLayout

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            //左右將需要的空間空出來(寬度取邊框筆觸寬度和邊框弧度的較大值)
            left = (int) (1.1 * Math.max(borderWidth, radius));
            right = (int) (getMeasuredWidth() - 1.1 * Math.max(borderWidth, radius));
            top = getPaddingTop();
            bottom = top + child.getMeasuredHeight();

            child.layout(
                    left,
                    top,
                    right,
                    bottom
            );
        }
    }

這里需要注意芭届,并不會(huì)因?yàn)槲覀冊O(shè)置了paddingTop和paddingBottom储矩,onLayout的top和bottom值就按照padding過后的值給過來,所以我們要自己計(jì)算

繪制--onDraw

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //設(shè)置空心褂乍,因?yàn)橐嬁招膱A角矩形
        paint.setStyle(Paint.Style.STROKE);
        //設(shè)置筆畫粗細(xì)度
        paint.setStrokeWidth(borderWidth);
        paint.setColor(borderColor);
        //開啟抗鋸齒
        paint.setAntiAlias(true);
        canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));

        /**
         * 因?yàn)槔L畫的時(shí)候持隧,設(shè)定的xy點(diǎn)是筆觸的中點(diǎn)
         * 所以left right bottom都需要+-筆觸寬度的一半(borderWidth / 2),保證畫出來的邊框不會(huì)有一邊在視線以外并且貼邊
         * top是例外逃片,因?yàn)橛形淖致挪Γ砸猤etPaddingTop()/2
         */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            canvas.drawRoundRect(borderWidth / 2, getPaddingTop() / 2, getMeasuredWidth() - borderWidth / 2, getMeasuredHeight() - borderWidth / 2, radius, radius, paint);
        } else {
            canvas.drawRoundRect(new RectF(borderWidth / 2, getPaddingTop() / 2, getMeasuredWidth() - borderWidth / 2, getMeasuredHeight() - borderWidth / 2), radius, radius, paint);
        }

        if (!TextUtils.isEmpty(title)) {
            titleRect = Utils.getTextSize(textSizeTitle, title);
            /**
             * 畫一條線(顏色{@link #getBgColor()}),作為空白處用來畫title
             * 線的起始點(diǎn)X值:筆觸寬度+弧度值+用戶自定義標(biāo)題偏移量
             */
            int lineStartX = borderWidth + radius + titleOffset;
            //線的終點(diǎn)X值褥实,則需要計(jì)算文字寬度來決定
            int lineEndX = lineStartX;
            //這里計(jì)算的是控件剩余可用于繪制的寬度呀狼,如果根本沒地方了,直接結(jié)束繪制
            int availableWidth = getMeasuredWidth() - borderWidth - radius - lineStartX;
            if (availableWidth <= 0) {
                return;
            }
            int textWidth = titleRect.width();
            //如果標(biāo)題過寬损离,則裁剪標(biāo)題
            //-8dp是因?yàn)樾枰A(yù)留空白哥艇,防止標(biāo)題和邊框過于靠近
            if (textWidth > availableWidth - UnitConverter.dip2px(getContext(), 8)) {
                title = getSuitableText(title, textSizeTitle, availableWidth - UnitConverter.dip2px(getContext(), 6));
                titleRect = Utils.getTextSize(textSizeTitle, title);
                textWidth = titleRect.width();
            }
            if (textWidth * 0.2f > UnitConverter.dip2px(getContext(), 4)) {
                lineEndX += textWidth + UnitConverter.dip2px(getContext(), 4);
            } else {
                lineEndX += textWidth * 1.2f;
            }
            //標(biāo)題起始點(diǎn)X值=空白線起始值X +( 空白線長度 - 標(biāo)題長度 )/2
            //( 空白線長度 - 標(biāo)題長度 )/2 就是留白的長度
            int textStartX = lineStartX + (lineEndX - lineStartX - textWidth) / 2;

            paint.setStyle(Paint.Style.FILL);
            paint.setStrokeWidth(borderWidth);
            paint.setColor(getBgColor());
            canvas.drawLine(lineStartX, getPaddingTop() / 2, lineEndX, getPaddingTop() / 2, paint);

            paint.setTextSize(textSizeTitle);
            paint.setColor(textColorTitle);
            Paint.FontMetricsInt fm = paint.getFontMetricsInt();
            //文字的繪制,并不是以字高的中點(diǎn)為(x,y)僻澎,而是以baseLine為準(zhǔn)貌踏,所以位置并不好算,這里也是瞎蒙
            canvas.drawText(title, textStartX, ((fm.bottom - fm.ascent) / 3 + getPaddingTop() / 2), paint);
        }
    }

這里有一點(diǎn)需要注意窟勃,由于畫筆的筆觸是有寬度的(比如10dp這么寬)哩俭,那么你在繪制圖形的時(shí)候就要注意,startY并不是筆觸的最上方位置拳恋,比如說:

canvas.drawLine(lineStartX, 0, lineEndX, 0, paint);

如果這樣畫凡资,很可能這條線有一半會(huì)在視線之外

本來想講一下繪制的思路,但是好像沒啥必要谬运。隙赁。“鹋看代碼就差不多了伞访,就這樣,有問題評論區(qū)交流
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末轰驳,一起剝皮案震驚了整個(gè)濱河市厚掷,隨后出現(xiàn)的幾起案子弟灼,更是在濱河造成了極大的恐慌,老刑警劉巖冒黑,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件田绑,死亡現(xiàn)場離奇詭異,居然都是意外死亡抡爹,警方通過查閱死者的電腦和手機(jī)掩驱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冬竟,“玉大人欧穴,你說我怎么就攤上這事”门梗” “怎么了涮帘?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長笑诅。 經(jīng)常有香客問我调缨,道長,這世上最難降的妖魔是什么苟鸯? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮棚点,結(jié)果婚禮上早处,老公的妹妹穿的比我還像新娘。我一直安慰自己瘫析,他們只是感情好砌梆,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贬循,像睡著了一般咸包。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杖虾,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天烂瘫,我揣著相機(jī)與錄音,去河邊找鬼奇适。 笑死坟比,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嚷往。 我是一名探鬼主播葛账,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼皮仁!你這毒婦竟也來了籍琳?” 一聲冷哼從身側(cè)響起菲宴,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎趋急,沒想到半個(gè)月后喝峦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宣谈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年愈犹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闻丑。...
    茶點(diǎn)故事閱讀 40,444評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漩怎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嗦嗡,到底是詐尸還是另有隱情勋锤,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布侥祭,位于F島的核電站叁执,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏矮冬。R本人自食惡果不足惜谈宛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胎署。 院中可真熱鬧吆录,春花似錦、人聲如沸琼牧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巨坊。三九已至撬槽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趾撵,已是汗流浹背侄柔。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留占调,地道東北人勋拟。 一個(gè)月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像妈候,于是被迫代替她去往敵國和親敢靡。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 【Android 自定義View】 [TOC] 自定義View基礎(chǔ) 接觸到一個(gè)類苦银,你不太了解他啸胧,如果貿(mào)然翻閱源碼只...
    Rtia閱讀 3,959評論 1 14
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,275評論 25 707
  • 用兩張圖告訴你赶站,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,744評論 2 59
  • 前言:雖然夢想為了現(xiàn)實(shí)暫時(shí)會(huì)妥協(xié)纺念,但終有一天贝椿,它將會(huì)實(shí)現(xiàn) 很多人認(rèn)為自定義控件中最不能理解的就是onMeasure...
    阿瑞921閱讀 1,368評論 0 11
  • □文/莫曉煙雨 “一層秋雨一層涼”,每當(dāng)秋雨連綿日子來臨時(shí)陷谱,家鄉(xiāng)的秋天便真正的開始了烙博。 八百里秦川道上一個(gè)毫不起眼...
    莫曉煙雨閱讀 228評論 2 1