该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。


第六篇了,,这一篇我们来看自定义View的各种姿势。前面几篇文章中我们介绍了Acitivity的启动流程以及生命周期,还介绍了Activity显示的各种原理。那么这篇文章呢,我们来实战一下。(读者可能看了好多关于Android Activity相关的知识,也看了View的实现原理。可是对于自定义View还是感觉隔着一层膜,那么今天我们试着捅破这层隔膜。)

上篇文章中我们详细讲解了ViewRootImpl,我们知道了其5大过程,知道了View的测量、布局以及绘制。那么这些知识对我们有何用处呢。下面我们就来自定义View


Android本身的控件系统可以实现我们开发中的一些基本需求,可是我们在处理实际业务的时候却催生出了Android控件系统不能很好的需求。这时,自定义控件应运而生。

在进行自定义View之前我们先来看一下View的坐标系。



上图引自刘望舒大神的博客


第1种自定义View的姿势——直接继承自View,重写其onDraw方法

直接继承自View,重写其onDraw方法,这个方式主要用来实现一些不规则的效果。比如显示一个圆。需要注意的是直接继承自View的控件需要对支持wrap_content和padding做处理。所以本例中也重写了onMeasure方法。以及在onDraw方法中加入了自身padding的处理。读者可试着去除onMeasure方法或者onDraw方法中的对padding的处理看看效果

自定义的属性xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="CircleView">
  4. <attr name="circle_color" format="color" />
  5. </declare-styleable>
  6. </resources>

自定义CircleView

  1. import android.content.Context;
  2. import android.content.res.TypedArray;
  3. import android.graphics.Canvas;
  4. import android.graphics.Color;
  5. import android.graphics.Paint;
  6. import android.util.AttributeSet;
  7. import android.view.View;
  8. public class CircleView extends View {
  9. private int mColor = Color.RED;
  10. private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  11. public CircleView(Context context) {
  12. super(context);
  13. init();
  14. }
  15. public CircleView(Context context, AttributeSet attrs) {
  16. this(context, attrs, 0);
  17. }
  18. public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
  19. super(context, attrs, defStyleAttr);
  20. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
  21. mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
  22. a.recycle();
  23. init();
  24. }
  25. private void init() {
  26. mPaint.setColor(mColor);
  27. }
  28. @Override
  29. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  30. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  31. int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
  32. int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
  33. int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
  34. int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
  35. if (widthSpecMode == MeasureSpec.AT_MOST
  36. && heightSpecMode == MeasureSpec.AT_MOST) {
  37. setMeasuredDimension(200, 200);
  38. } else if (widthSpecMode == MeasureSpec.AT_MOST) {
  39. setMeasuredDimension(200, heightSpecSize);
  40. } else if (heightSpecMode == MeasureSpec.AT_MOST) {
  41. setMeasuredDimension(widthSpecSize, 200);
  42. }
  43. }
  44. @Override
  45. protected void onDraw(Canvas canvas) {
  46. super.onDraw(canvas);
  47. final int paddingLeft = getPaddingLeft();
  48. final int paddingRight = getPaddingRight();
  49. final int paddingTop = getPaddingTop();
  50. final int paddingBottom = getPaddingBottom();
  51. int width = getWidth() - paddingLeft - paddingRight;
  52. int height = getHeight() - paddingTop - paddingBottom;
  53. int radius = Math.min(width, height) / 2;
  54. canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2,
  55. radius, mPaint);
  56. }
  57. }

使用自定义CircleView

布局文件activity_main1.xml

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:app="http://schemas.android.com/apk/res-auto"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:background="#ffffff"
  6. android:orientation="vertical" >
  7. <com.mafeibiao.testapplication.CircleView
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:padding="20dp"
  11. android:background="@color/light_green"/>
  12. </LinearLayout>

MainActivity.java

  1. package com.mafeibiao.testapplication;
  2. import android.support.v7.app.AppCompatActivity;
  3. import android.os.Bundle;
  4. public class MainActivity extends AppCompatActivity {
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_main1);
  9. }
  10. }

效果如下图

注:我们在这里直接继承了View并重写其onMeasure和onDraw方法,我们从上几篇文章详细分析了Activity的创建以及显示。我们在梳理一下,首先程序的入口函数是ActivityThread.main函数,从这个函数开始,然后回调我们MainActivity的attach函数,我们在这里没有重写这个函数,但是该函数内部会创建一个至关重要的对象PhoneWindow,然后会回调我们MainActivity的onCreate函数,我们在MainActivity的onCreate函数中调用了setContentView(R.layout.activity_main1);这个函数内部会创建Android 的顶级View DecorView,把我们的布局文件R.layout.activity_main1解析成相关View并关联到DecorView下。然后会调用WindowManager的addView方法把DecorView添加到PhoneWindow上,实际上完成这个过程的是ViewRootImpl,它会对我们的DecorView依次进测量、布局、绘制等工作,在这些工作的过程中会依次回调我们在View以及其子类中重写的onMeasure、onLayout、onDraw等方法。以我们上面的CircleView为例,,我们在布局文件中定义了一个LinearLayout并在LinearLayout内使用了我们自定义的CircleView,那么按照上一章讲解ViewRootImpl的工作流程。会沿着控件树从上到下依次调用到我们自定义的CircleView onMeasure(我们重写了该方法)然后沿着控件树从下向上依次回调。上文也讲过,测量过程是后根遍历,布局过程是先根遍历。(要理解Android View的层级结构是树结构


第2种自定义View的姿势——直接继承自Android中控件View,如TextView或者EditText等。

下面我们来实现渐变的TextView。这个我们效果我们经常在锁屏应用上看到。

  1. import android.content.Context;
  2. import android.graphics.Canvas;
  3. import android.graphics.Color;
  4. import android.graphics.LinearGradient;
  5. import android.graphics.Matrix;
  6. import android.graphics.Paint;
  7. import android.graphics.Shader;
  8. import android.support.v7.widget.AppCompatTextView;
  9. import android.util.AttributeSet;
  10. import android.util.Log;
  11. public class CustomTextView extends AppCompatTextView {
  12. private final static String TAG = CustomTextView.class.getSimpleName();
  13. private Paint paint1;
  14. private Paint paint2;
  15. private int mWidth;
  16. private LinearGradient gradient;
  17. private Matrix matrix;
  18. //渐变的速度
  19. private int deltaX;
  20. public CustomTextView(Context context) {
  21. super(context, null);
  22. }
  23. public CustomTextView(Context context, AttributeSet attrs) {
  24. super(context, attrs);
  25. initView(context, attrs);
  26. }
  27. private void initView(Context context, AttributeSet attrs) {
  28. paint1 = new Paint();
  29. paint1.setColor(getResources().getColor(android.R.color.holo_blue_dark));
  30. paint1.setStyle(Paint.Style.FILL);
  31. }
  32. @Override
  33. protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  34. super.onSizeChanged(w, h, oldw, oldh);
  35. if(mWidth == 0){
  36. Log.e(TAG,"*********************");
  37. mWidth = getMeasuredWidth();
  38. paint2 = getPaint();
  39. //颜色渐变器
  40. gradient = new LinearGradient(0, 0, mWidth, 0, new int[]{Color.GRAY,Color.WHITE,Color.GRAY}, new float[]{
  41. 0.3f,0.5f,1.0f
  42. }, Shader.TileMode.CLAMP);
  43. paint2.setShader(gradient);
  44. matrix = new Matrix();
  45. }
  46. }
  47. @Override
  48. protected void onDraw(Canvas canvas) {
  49. super.onDraw(canvas);
  50. if(matrix !=null){
  51. deltaX += mWidth / 5;
  52. if(deltaX > 2 * mWidth){
  53. deltaX = -mWidth;
  54. }
  55. }
  56. //关键代码通过矩阵的平移实现
  57. matrix.setTranslate(deltaX, 0);
  58. gradient.setLocalMatrix(matrix);
  59. postInvalidateDelayed(100);
  60. }
  61. }

下面我们来实现支付宝上手机号和银行卡号写入分段的效果。继承自EditText





如上图,在作为手机号或者银行卡时输入的数字会按照不同规则分段,并且右侧出现清空按钮。很明显,我们需要自定义一个控件符合上述要求。

style格式attrs.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="MyEditText">
  4. <!-- 设置自定义输入框的模式 当值为1:普通输入框模式 2:银行卡号输入框模式 3:电话号码模式 默认为1-->
  5. <attr name="editTextMode" format="integer" />
  6. <!-- 配置自定义控件在银行卡号模式下分隔位数 ,默认为4位 -->
  7. <attr name="splitNumber" format="integer" />
  8. </declare-styleable>
  9. </resources>

布局代码activity_main.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:background="#ffffff"
  7. android:orientation="vertical" >
  8. <TextView
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:layout_marginBottom="5dp"
  12. android:layout_marginLeft="5dp"
  13. android:layout_marginRight="5dp"
  14. android:layout_marginTop="20dp"
  15. android:text="请绑定持卡人本人的银行卡" />
  16. <LinearLayout
  17. android:id="@+id/ll_name"
  18. android:layout_width="match_parent"
  19. android:layout_height="wrap_content"
  20. android:layout_marginBottom="1dp"
  21. android:background="#ffffff"
  22. android:gravity="center_vertical"
  23. android:orientation="horizontal"
  24. android:paddingBottom="5dp"
  25. android:paddingLeft="10dp"
  26. android:paddingRight="10dp"
  27. android:paddingTop="5dp" >
  28. <TextView
  29. android:layout_width="0dp"
  30. android:layout_height="wrap_content"
  31. android:layout_weight="1"
  32. android:gravity="left"
  33. android:text="持卡人"
  34. />
  35. <com.mafeibiao.testapplication.MyEditText
  36. android:id="@+id/et_name"
  37. android:layout_width="0dp"
  38. android:layout_height="wrap_content"
  39. android:layout_weight="5"
  40. android:hint="请输入姓名"
  41. android:padding="5dp" >
  42. </com.mafeibiao.testapplication.MyEditText>
  43. </LinearLayout>
  44. <LinearLayout
  45. android:id="@+id/ll_card_number"
  46. android:layout_width="match_parent"
  47. android:layout_height="wrap_content"
  48. android:background="#ffffff"
  49. android:gravity="center_vertical"
  50. android:orientation="horizontal"
  51. android:paddingBottom="5dp"
  52. android:paddingLeft="10dp"
  53. android:paddingRight="10dp"
  54. android:paddingTop="5dp" >
  55. <TextView
  56. android:layout_width="0dp"
  57. android:layout_height="wrap_content"
  58. android:layout_weight="1"
  59. android:gravity="left"
  60. android:text="卡号" />
  61. <com.mafeibiao.testapplication.MyEditText
  62. android:id="@+id/et_card_number"
  63. android:layout_width="0dp"
  64. android:layout_height="wrap_content"
  65. android:layout_weight="5"
  66. android:hint="请输入银行卡号"
  67. android:padding="5dp"
  68. android:inputType="number"
  69. app:editTextMode="2"
  70. app:splitNumber="4">
  71. </com.mafeibiao.testapplication.MyEditText>
  72. </LinearLayout>
  73. <LinearLayout
  74. android:id="@+id/ll_phone_number"
  75. android:layout_width="match_parent"
  76. android:layout_height="wrap_content"
  77. android:background="#ffffff"
  78. android:gravity="center_vertical"
  79. android:orientation="horizontal"
  80. android:paddingBottom="5dp"
  81. android:paddingLeft="10dp"
  82. android:paddingRight="10dp"
  83. android:paddingTop="5dp" >
  84. <TextView
  85. android:layout_width="0dp"
  86. android:layout_height="wrap_content"
  87. android:layout_weight="1"
  88. android:gravity="left"
  89. android:text="手机号" />
  90. <com.mafeibiao.testapplication.MyEditText
  91. android:id="@+id/et_phone_number"
  92. android:layout_width="0dp"
  93. android:layout_height="wrap_content"
  94. android:layout_weight="5"
  95. android:hint="请输入手机号"
  96. android:inputType="number"
  97. android:padding="5dp"
  98. app:editTextMode="3"
  99. >
  100. </com.mafeibiao.testapplication.MyEditText>
  101. </LinearLayout>
  102. </LinearLayout>

代码MainActivity.java

  1. package com.mafeibiao.testapplication;
  2. /**
  3. * Created by mafeibiao on 2017/11/21.
  4. */
  5. import android.content.Context;
  6. import android.content.res.TypedArray;
  7. import android.graphics.Rect;
  8. import android.graphics.drawable.Drawable;
  9. import android.support.v7.widget.AppCompatEditText;
  10. import android.text.Editable;
  11. import android.text.TextWatcher;
  12. import android.util.AttributeSet;
  13. import android.view.MotionEvent;
  14. public class MyEditText extends AppCompatEditText {
  15. // 每隔多少位以空格进行分隔一次,卡号一般都是每4位以空格分隔一次
  16. public int splitNumber = 4;
  17. // 自定义输入框的模式 当值为true:银行卡号输入框模式,false:普通输入框模式
  18. private int editTextMode = 1;
  19. public MyEditText(Context context) {
  20. this(context, null);
  21. }
  22. public MyEditText(Context context, AttributeSet attrs) {
  23. this(context, attrs, 0);
  24. }
  25. public MyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
  26. super(context, attrs, defStyleAttr);
  27. init(attrs);
  28. }
  29. // 内容清除图标
  30. private Drawable mClearDrawable;
  31. /**
  32. * 初始化方法
  33. */
  34. private void init(AttributeSet attrs) {
  35. // 设置单行显示所有输入框内容
  36. setSingleLine();
  37. // 设置输入框可获得焦点
  38. setFocusable(true);
  39. setFocusableInTouchMode(true);
  40. TypedArray t = this.getResources().obtainAttributes(attrs, R.styleable.MyEditText);
  41. editTextMode = t.getInt(R.styleable.MyEditText_editTextMode, editTextMode);
  42. splitNumber = t.getInt(R.styleable.MyEditText_splitNumber, splitNumber);
  43. t.recycle();
  44. mClearDrawable = this.getResources().getDrawable(R.drawable.clear);
  45. mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(), mClearDrawable.getIntrinsicHeight());
  46. initEvent();
  47. }
  48. // 输入框内容改变后onTextChanged方法会调用多次,设置一个变量让其每次改变之后只调用一次
  49. private boolean isTextChanged = false;
  50. /**
  51. * 处理事件的方法
  52. */
  53. private void initEvent() {
  54. addTextChangedListener(new TextWatcher() {
  55. @Override
  56. public void onTextChanged(CharSequence s, int start, int before, int count) {
  57. if (isTextChanged) {
  58. isTextChanged = false;
  59. return;
  60. }
  61. isTextChanged = true;
  62. // 处理输入内容空格与位数以及光标位置的逻辑
  63. handleInputContent(s, start,before,count);
  64. // 处理清除图标的显示与隐藏逻辑
  65. handleClearIcon(true);
  66. }
  67. @Override
  68. public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  69. }
  70. @Override
  71. public void afterTextChanged(Editable s) {
  72. }
  73. });
  74. }
  75. // 卡号内容
  76. private String content;
  77. // 卡号最大长度,卡号一般最长19位
  78. public static final int MAX_CARD_NUMBER_LENGHT = 19;
  79. //手机号
  80. public static final int MAX_PHONE_NUMBER_LENGHT = 11;
  81. // 缓冲分隔后的新内容串
  82. private String result = "";
  83. /**
  84. * 处理输入内容空格与位数的逻辑
  85. */
  86. private void handleInputContent(CharSequence s, int start, int before, int count) {
  87. content = s.toString();
  88. // 先缓存输入框内容
  89. result = content;
  90. // 去掉空格,以防止用户自己输入空格
  91. content = content.replace(" ", "");
  92. switch (editTextMode){
  93. case 1://普通模式
  94. break;
  95. case 2://银行卡号模式
  96. // 限制输入的数字位数最多21位(银行卡号一般最多21位)
  97. if (content != null && content.length() <= MAX_CARD_NUMBER_LENGHT) {
  98. result = "";
  99. int i = 0;
  100. // 先把splitNumber倍的字符串进行分隔
  101. while (i + splitNumber < content.length()) {
  102. result += content.substring(i, i + splitNumber) + " ";
  103. i += splitNumber;
  104. }
  105. // 最后把不够splitNumber倍的字符串加到末尾
  106. result += content.substring(i, content.length());
  107. } else {
  108. // 如果用户输入的位数
  109. result = result.substring(0, result.length() - 1);
  110. }
  111. break;
  112. case 3://手机号模式
  113. if (content != null && content.length() <= MAX_PHONE_NUMBER_LENGHT) {
  114. int length = s.toString().length();
  115. if (length == 3 || length == 8){
  116. result += " ";
  117. }
  118. } else {
  119. // 如果用户输入的位数
  120. result = result.substring(0, result.length() - 1);
  121. }
  122. break;
  123. }
  124. // 获取光标开始位置
  125. // 必须放在设置内容之前
  126. int j = getSelectionStart();
  127. setText(result);
  128. // 处理光标位置
  129. handleCursor(before, j);
  130. }
  131. /**
  132. * 处理光标位置
  133. *
  134. * @param before
  135. * @param j
  136. */
  137. private void handleCursor(int before, int j) {
  138. // 处理光标位置
  139. try {
  140. if (j + 1 < result.length()) {
  141. // 添加字符
  142. if (before == 0) {
  143. // 遇到空格,光标跳过空格,定位到空格后的位置
  144. if (j % splitNumber + 1 == 0) {
  145. setSelection(j + 1);
  146. } else {
  147. // 否则,光标定位到内容之后 (光标默认定位方式)
  148. setSelection(result.length());
  149. }
  150. // 回退清除一个字符
  151. } else if (before == 1) {
  152. // 回退到上一个位置(遇空格跳过)
  153. setSelection(j);
  154. }
  155. } else {
  156. MyEditText.this.setSelection(result.length());
  157. }
  158. } catch (Exception e) {
  159. }
  160. }
  161. /**
  162. * 处理清除图标的逻辑
  163. */
  164. private void handleClearIcon(boolean focused) {
  165. if (content != null && content.length() > 0) {
  166. // 显示
  167. if (focused) {
  168. setEditTextIcon(null, null, mClearDrawable, null);
  169. } else {
  170. // 隐藏
  171. setEditTextIcon(null, null, null, null);
  172. }
  173. } else {
  174. // 隐藏
  175. setEditTextIcon(null, null, null, null);
  176. }
  177. }
  178. @Override
  179. public boolean onTouchEvent(MotionEvent event) {
  180. // 获取用户点击的坐标,这里只对X轴做了判断,
  181. float x = event.getX();
  182. // 当用户抬起手指时,判断坐标是否在图标交互区域,如果在则清空输入框内容,同时隐藏图标自己
  183. if (event.getAction() == MotionEvent.ACTION_UP) {
  184. if (x > (getWidth() - getPaddingRight() - mClearDrawable.getIntrinsicWidth())) {
  185. // 清空输入框内容
  186. setText("");
  187. // 隐藏图标
  188. setEditTextIcon(null, null, null, null);
  189. }
  190. }
  191. return super.onTouchEvent(event);
  192. }
  193. @Override
  194. protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
  195. super.onFocusChanged(focused, direction, previouslyFocusedRect);
  196. handleClearIcon(focused);
  197. //刷新界面,防止有时候出现的不刷新界面情况
  198. invalidate();
  199. }
  200. /**
  201. * 设置输入框的左,上,右,下图标
  202. *
  203. * @param left
  204. * @param top
  205. * @param right
  206. * @param bottom
  207. */
  208. private void setEditTextIcon(Drawable left, Drawable top, Drawable right, Drawable bottom) {
  209. setCompoundDrawables(left, top, right, bottom);
  210. }
  211. /**
  212. * 重写onMeasure,主要目的是让EditText的高度与我们显示在右侧的清空图标的高度相同,否则输入的时候可能会动态改变EditText的高度以适应清空图标的高度
  213. * 用户体验不好
  214. * @param widthMeasureSpec
  215. * @param heightMeasureSpec
  216. */
  217. @Override
  218. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  219. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  220. int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
  221. int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
  222. int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
  223. int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
  224. if (widthSpecMode == MeasureSpec.AT_MOST
  225. && heightSpecMode == MeasureSpec.AT_MOST) {
  226. setMeasuredDimension(mClearDrawable.getIntrinsicWidth(), mClearDrawable.getIntrinsicHeight());
  227. } else if (widthSpecMode == MeasureSpec.AT_MOST) {
  228. setMeasuredDimension(mClearDrawable.getIntrinsicWidth(), heightSpecSize);
  229. } else if (heightSpecMode == MeasureSpec.AT_MOST) {
  230. setMeasuredDimension(widthSpecSize, mClearDrawable.getIntrinsicHeight());
  231. }
  232. }
  233. }

本篇总结

本篇文章上了一些例子来巩固我们之前学过的知识。我们自定义了CircleView来实现一个圆,该CircleView直接继承自View,需要注意的是直接继承自View的控件需要自己实现对wrap_content和padding的支持,另外我们继承了TextView和EditText来分别实现了渐变的TextView和银行卡手机号输入分隔的问题。可以看到我们继承自这些系统控件时,并单独没有实现对wrap_content和padding的支持(后面的自定义EditText不实现onMeasure方法也不会对wrap_content和padding造成影响)


下篇预告

在下一篇文章中我们将来分析Android View的事件体系,这篇文章已经涉及到了一些,不过读者对Android View的事件体系可能还是有些模糊。并且读者可能已经发现了,本篇自定义View并没有继承ViewGroup和其子类LinearLayout,FrameLayout等,因为这部分内容我们会在分析了Android View的事件体系之后再做解释。


此致,敬礼

Android开发之漫漫长途 番外篇——自定义View的各种姿势1的更多相关文章

  1. Android开发之漫漫长途 番外篇——自定义View的各种姿势2

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  2. Android开发之漫漫长途 番外篇——内存泄漏分析与解决

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  3. Android开发之漫漫长途 XI——从I到X的小结

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  4. Android开发之漫漫长途 Fragment番外篇——TabLayout+ViewPager+Fragment

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  5. Android开发之漫漫长途 XIV——RecyclerView

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  6. Android开发之漫漫长途 XV——RecyclerView

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  7. Android开发之漫漫长途 Ⅰ——Android系统的创世之初以及Activity的生命周期

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>中的相关知识,再次表示该书 ...

  8. Android开发之漫漫长途 Ⅱ——Activity的显示之Window和View(2)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  9. Android开发之漫漫长途 Ⅳ——Activity的显示之ViewRootImpl初探

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

随机推荐

  1. Ubuntu Mac OS主题分享

    Ubuntu Mac OS主题分享 一直想搞一个Mac OS主题试试,结果很悲催,在网上搜索的Macbuntu主题在安装主题(macbuntu-os-themes-Its-v7)和 图标(macbun ...

  2. Cosmos OpenSSD架构分析--FSC

    接口速度: type   bw  read 75μs 1s/75μs*8k/1s=104m/s write 1300μs   1s/1300μs*8k/1s=6m/s erase 3.8ms  1s/ ...

  3. jQuery 常用操作(转)

    一.书写规则 支持链式操作: 在变量前加"$"符号(var $variable = jQuery 对象): 注:此规定并不是强制要求. 二.寻找元素 选择器 基本选择器 层级选择器 ...

  4. TsBatis 预览

    前言 在发布了 mybatis-dynamic-query之后感觉基本功能稳定了,而且现在在工作项目开发效率大大提高,而且非常易于维护. 最近准备带几个小朋友以前用typescript 打通前后端,当 ...

  5. jQuery Ajax跨域问题简易解决方案

    场景:由于业务需求,需要在一个页面显示另外一个页面,并且右键查看源代码看不到一条链接. 实现方式:使用iframe来显示这个首页,至于首页的地址则使用jQuery Ajax来获取.html代码如下: ...

  6. Java Swing学习

    在Java学习的过程中,我们时常会因为控制台程序的枯燥而失去了学习Java的乐趣,那么今天我们就开始学习Java的Swing.也就是GUI(Graphical user interface),在应用到 ...

  7. 基于ElementUI的网站换主题的一些思考与实现

    前言 web应用程序,切换主题,给其换肤,是一个比较常见的需求. 如何能快速的切换主题色?(只有固定的一种皮肤) 如果又想把主题色切换为以前的呢?(有多种可切换的皮肤) 该以何种方式编写标签的css属 ...

  8. LeetCode 101. Symmetric Tree (对称树)

    Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center). For e ...

  9. Vue-cli安装教程

    第一步:安装vue-cli npm install vue-cli -g -g :代表全局安装.如果你安装时报错,一般是网络问题,你可以尝试用cnpm来进行安装. 检测是否安装成功:可以用vue -V ...

  10. [板子]Floyd&Dijkstra

    谨以此笔记记录jjw高三党四个月学习NOI的历程..如转载请标记出处 Floyd算法: 默认是业界最短路最简单的写法,并且只有五行.时间复杂度为O(N3),空间复杂度为O(N2). ;k<=n; ...