1. 程式人生 > >Android -- 從原始碼的角度一步步打造自己的TextView

Android -- 從原始碼的角度一步步打造自己的TextView

package com.qianmo.activitydetail;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import java.util.ArrayList;

/**
 * Created by wangjitao on 2017/3/20 0020.
 * E-Mail:
[email protected]
*/ public class MyTextView4 extends View { private final static String TAG = "MyTextView"; //文字 private String mText; private ArrayList<String> mTextList; //文字的顏色 private int mTextColor; //文字的大小 private float mTextSize; //繪製的範圍 private Rect mBound; private Paint mPaint; public MyTextView4(Context context) { this(context, null); } public MyTextView4(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyTextView4(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr); } /** * 初始化資料 */ private void init(Context context, AttributeSet attrs, int defStyleAttr) { //獲取自定義屬性的值 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyTextView2, defStyleAttr, 0); mText = a.getString(R.styleable.MyTextView2_myText); mTextColor = a.getColor(R.styleable.MyTextView2_myTextColor, Color.BLACK); mTextSize = a.getDimension(R.styleable.MyTextView2_myTextSize, 30f); a.recycle(); //初始化Paint資料 mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(mTextColor); mPaint.setTextSize(mTextSize); //獲取繪製的寬高 mBound = new Rect(); mTextList = new ArrayList<>(); mPaint.getTextBounds(mText, 0, mText.length(), mBound); } @Override protected void onDraw(Canvas canvas) { //繪製文字 for (int i = 0; i < mTextList.size(); i++) { mPaint.getTextBounds(mTextList.get(i), 0, mTextList.get(i).length(), mBound); Log.v(TAG, "在X:" + (getWidth() / 2 - mBound.width() / 2) + " Y:" + (getPaddingTop() + (mBound.height() * i)) + " 繪製:" + mTextList.get(i)); canvas.drawText(mTextList.get(i), (getWidth() / 2 - mBound.width() / 2), (getPaddingTop() + (mBound.height() * (i + 1))), mPaint); Log.i(TAG, "getWidth() :" + getWidth() + ", mBound.width():" + mBound.width() + ",getHeight:" + getHeight() + ",mBound.height() *i:" + mBound.height() * i); } } boolean isOneLine = true; float lineNum; float splineNum; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); //獲取寬的模式 int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 獲取高的模式 int widthSize = MeasureSpec.getSize(widthMeasureSpec); //獲取寬的尺寸 int heightSize = MeasureSpec.getSize(heightMeasureSpec); //獲取高的尺寸 Log.i(TAG, "widthMode:" + widthMode); Log.i(TAG, "heightMode:" + heightMode); Log.i(TAG, "widthSize:" + widthSize); Log.i(TAG, "heightSize:" + heightSize); float textWidth = mBound.width(); //文字的寬度 if (mTextList.size() == 0) { //將文字分段 int padding = getPaddingLeft() + getPaddingRight(); int specWidth = widthSize - padding; //剩餘能夠顯示文字的最寬度 if (textWidth <= specWidth) { //可以顯示一行 lineNum = 1; mTextList.add(mText); } else { //超過一行 isOneLine = false; splineNum = textWidth / specWidth; //如果有小數的話則進1 if ((splineNum + "").contains(".")) { lineNum = (int) (splineNum + 0.5); // lineNum = Integer.parseInt((splineNum + "").substring(0, (splineNum + "").indexOf("."))) + 1; } else { lineNum = splineNum; } int lineLength = (int) (mText.length() / splineNum); for (int i = 0; i < lineNum; i++) { String lineStr; //判斷是否可以一行展示 if (mText.length() < lineLength) { lineStr = mText.substring(0, mText.length()); } else { lineStr = mText.substring(0, lineLength); } mTextList.add(lineStr); if (!TextUtils.isEmpty(mText)) { if (mText.length() < lineLength) { mText = mText.substring(0, mText.length()); } else { mText = mText.substring(lineLength, mText.length()); } } else { break; } } } } //下面對wrap_content這種模式進行處理 int width; int height; if (widthMode == MeasureSpec.EXACTLY) { width = widthSize; } else { //如果是wrap_content,我們要得到控制元件需要多大的尺寸 if (isOneLine) { //控制元件的寬度就是文字的寬度加上兩邊的內邊距。內邊距就是padding值,在構造方法執行完就被賦值 width = (int) (getPaddingLeft() + textWidth + getPaddingRight()); } else { //如果是多行,說明控制元件寬度應該填充父窗體 width = widthSize; } } if (heightMode == MeasureSpec.EXACTLY) { height = heightSize; } else { //如果是wrap_content,我們要得到控制元件需要多大的尺寸 //首先丈量文字的寬度 float textHeight = mBound.height(); if (isOneLine) { //控制元件的寬度就是文字的寬度加上兩邊的內邊距。內邊距就是padding值,在構造方法執行完就被賦值 height = (int) (getPaddingTop() + textHeight + getPaddingBottom()); } else { //如果是多行 height = (int) (getPaddingTop() + textHeight * lineNum + getPaddingBottom()); } } //儲存丈量結果 setMeasuredDimension(width, height); } }

相關推薦

Android -- 原始碼角度步步打造自己TextView

package com.qianmo.activitydetail; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.

步步打造自己的linux命令行計算器

參數轉換 時間 quit all 十進制 art pyc format chang 相信很多人,在工作中會需要使用到計算器。一般的做法是,打開並使用系統自帶的計算器。 這種做法可能對我來說,有如下幾個問題。 太慢。每次需要打開計算器,然後改成編程模式,手工選擇進制,再使用

步步打造自己的linux命令列計算器

相信很多人,在工作中會需要使用到計算器。一般的做法是,開啟並使用系統自帶的計算器。 這種做法可能對我來說,有如下幾個問題。 太慢。每次需要開啟計算器,然後改成程式設計模式,手工選擇進位制,再使用輸入表示式進行計算。 需要切換視窗。程式設計時經常是在終端中,使用GUI計算器則意味著要離開終端,計算完畢再切換回

Android教你如何步步打造通用介面卡

前言 在Android開發中ListView是最為常用的控制元件之一,基本每個應用都會涉及到它,要使用ListView列表展示,就不可避免地涉及到另外一個東西——Adapter,我們都知道,Adapt

Android 應用開發----7. ViewPager+Fragment步步打造頂部導航介面滑動效果

ViewPager+Fragment一步步打造頂部導航介面滑動效果 在許多應用中,我們常常用到這麼一個效果: 可以看到,由於現在的應用資料經常需要涉及到多個模組,所以常常需要使用滑動標籤在多個頁面之間跳轉,實現這樣的效果有很多種方式(比如系統自帶的tabhost控

原始碼角度分析Android系統的異常捕獲機制是如何執行的

我們在開發的時候經常會遇到各種異常,當程式遇到異常,便會將異常資訊拋到LogCat中,那這個過程是怎麼實現的呢? 我們以一個例子開始: import android.app.Activity; import android.os.Bundle; public clas

Android 原始碼角度分析——為什麼要用newInstance來例項化Fragment

最近在看Google技術文件的時候發現了一種新的方式來例項化Fragment,就是採用靜態工廠的方式建立Fragment。我們在使用Android studio建立一個類的時候,選擇New ->Fragment->Fragment(Blank)可以很

Android View 繪製流程 與invalidate 和postInvalidate 分析--原始碼角度

整個View樹的繪製流程是在ViewRootImpl.java類的performTraversals()函式展開的,該函式做的執行過程可簡單概況為  根據之前設置的狀態,判斷是否需要重新計算檢視大小(measure)、是否重新需要佈局檢視的位置(layout

ScratchView:步步打造萬能的 Android 刮獎效果控制元件

前言 我身邊有一部分開發的小夥伴,存在著這樣一種習慣。某一天,突然看到某一款 App 上有個很漂亮的自定義控制元件(動畫)效果,就會絞盡腦子想辦法去自己實現一發。當然,我自己也是屬於這型別的騷年,看到某種效果就會手癢難耐琢磨著實現套路。個人覺得這是一種需求驅動進步

Android原始碼角度看Handler機制

在Android開發規範中,規定了主執行緒的任務的響應時間不能超過5s,否則會出現ANR,即程式無響應。為了避免這個問題的出現,常用的一個解決方案就是開闢新執行緒,在開闢出來的子執行緒中去處理耗時的業務,然後回到UI執行緒(主執行緒)來重新整理UI,這個過程中“

富士康是如何大陸公司步步成為大陸企業的?

富士康據國內媒體報道,富士康旗下工業富聯公司於6月8日在上海掛牌,招股書顯示,工業富聯IPO發行股份數為19.695億股,發行價為13.77元,總共募集資金272.53億元。本次IPO衍生出了大量的話題,先是打破中國A股審批速度的最快紀錄,掛牌之後的第二個交易日又大漲44%,一躍成為A股市值最高的科技企業,此

yoloV3步步訓練自己的數據

圖片 cto file 目錄 好的 下載 ima 配置 自動 YOLOV3的主頁: https://pjreddie.com/darknet/yolo/ 運行主頁上的代碼得到: 首先使用一個開源的神經網絡框架Darknet,使用C和CUDA,有CPU和GPU兩種模式。

不使用 vue-cli 與 vue 模版,使用 Vue2.x + webpack4.x 零開始步步搭建專案框架

說明 這是我根據慕課網上的一個課程 Vue+Webpack打造todo應用 過程一步步搭下來的框架,去掉了業務相關的邏輯。 專案最終的效果包括了引入vue框架;使用CSS前處理器;使用babel;引用圖片等靜態資源;區分開發環境與生成環境,並做相應優化等。基本接近真正做專案時候的配置

原始碼角度看Spring生命週期(官方最全)

Spring在beanfactory中給出了spring的生命週期的list列表 一、bean初始化前的處理 Bean factory implementations should support the standard bean lifecycle interfaces as

原始碼角度解析 - ScrollView巢狀ListView只顯示一行的問題

<ScrollView android:id="@+id/scroll_view" android:layout_width="match_parent" android:layout_height="match_parent">

原始碼角度解析 - ScrollView巢狀ViewPager不顯示的問題

<ScrollView android:id="@+id/scroll_view" android:layout_width="match_parent" android:layout_height="match_parent">

原始碼角度深入理解OKHttp3

在日常開發中網路請求是很常見的功能。OkHttp作為Android開發中最常用的網路請求框架,在Android開發中我們經常結合retrofit一起使用,俗話說得好:“知其然知其所以然”,所以這篇文章我們通過原始碼來深入理解OKHttp3(基於3.12版本) 常規使用 在瞭

原始碼角度理解Java設計模式--責任鏈模式

本文內容思維導圖如下:                                        

帶你原始碼角度分析ViewGroup中事件分發流程

序言 這篇博文不是對事件分發機制全面的介紹,只是從原始碼的角度分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件在ViewGroup中的分發邏輯,瞭解各個事件在ViewGroup的分發邏輯對理解、解決滑動衝突問題很有幫助。 ViewGroup中事件分發流

【面試必備】透過原始碼角度步帶你分析 ArrayList 擴容機制

該文已加入開源文件:JavaGuide(一份涵蓋大部分Java程式設計師所需要掌握的核心知識)。地址:https://github.com/Snailclimb... 一 先從 ArrayList 的建構函式說起 ArrayList有三種方式來初始化,構造方法原始