編輯:關於Android編程
最近不是很忙,平常在家無聊時看看直播,總會看到一些新奇的驗證登錄方式,剛好自己也要熟悉一下新的開發工具android studio,所以打算自己實現一個.
一、確認需求
先看一下要到達的效果,如圖 :

滑塊圖片做的有點low,見諒.
分析一下,我們要實現這個功能需要什麼
首先需要一個滾動拖拽條,這個簡單,android系統給我們提供了原生SeekBar,裡邊的功能也比較齊全.
然後看一下我們重點要實現的功能,實現圖片上的驗證功能.我們分步來看
1.要在圖片上隨機位置顯示出一個陰影部分
2.要生成一個帶有邊框的並且符合陰影部分的驗證滑塊
3.生成的滑塊和陰影部分,角度隨機
4.生成的滑塊要隨著拖拽條一同滾動
5.驗證是否成功
二、思考解決方案
第一點,其實很簡單,我們只需要一張有透明度的陰影圖片,並且隨機生成一個坐標,遮擋住圖片即可.
第二點,可以用setXfermode中的圖像交叉模式來生成一個帶有邊框的滑塊.
第三點,可以通過改變圖像的matrix來實現對圖片的旋轉.
第四點,對外提供一個方法,來不斷改變滑塊的位置.
第五點,提供一個回調接口,來驗證是否成功.
三、代碼實現
有了上邊的思路,我們打算自定義一個DouYuView繼承自ImageView來實現功能.
先是創建一個attr文件定義一些自定義的屬性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--滑塊的高度-->
<attr name="unitHeight" format="dimension" />
<!--滑塊的寬度-->
<attr name="unitWidth" format="dimension" />
<!--滑塊占圖片高度的比例-->
<attr name="unitHeightScale" format="integer" />
<!--滑塊占圖片寬度的比例-->
<attr name="unitWidthScale" format="integer" />
<!--滑塊邊框的圖片資源-->
<attr name="unitShadeSrc" format="reference" />
<!--陰影部分的圖片資源-->
<attr name="unitShowSrc" format="reference" />
<!--是否需要旋轉-->
<attr name="needRotate" format="boolean" />
<!--驗證時的誤差值-->
<attr name="deviate" format="integer" />
<declare-styleable name="DouYuView">
<attr name="unitHeight" />
<attr name="unitWidth" />
<attr name="unitHeightScale" />
<attr name="unitWidthScale" />
<attr name="unitShadeSrc" />
<attr name="unitShowSrc" />
<attr name="needRotate" />
<attr name="deviate" />
</declare-styleable>
</resources>
代碼中的屬性及初始化
/**
* 定義畫筆
*/
private Paint mPaint;
/**
* 驗證的圖像
*/
private Bitmap mBitmap;
/**
* 驗證滑塊的高
*/
private int mUintHeight;
/**
* 驗證滑塊的寬
*/
private int mUintWidth;
/**
* 驗證滑塊寬占用整體圖片大小的比例,默認1/5
*/
private int mUnitWidthScale;
/**
* 驗證滑塊高度占用整體圖片大小的比例,默認1/4
*/
private int mUnitHeightScale;
/**
* 隨機生成滑塊的X坐標
*/
private int mUnitRandomX;
/**
* 隨機生成滑塊的Y坐標
*/
private int mUnitRandomY;
/***
* 滑塊移動的距離
*/
private float mUnitMoveDistance = 0;
/***
* 滑塊圖像
*/
private Bitmap mUnitBp;
/**
* 驗證位置圖像
*/
private Bitmap mShowBp;
/**
* 背景陰影圖像
*/
private Bitmap mShadeBp;
/**
* 是否需要旋轉
**/
private boolean needRotate;
/**
* 旋轉的角度
*/
private int rotate;
/**
* 判斷是否完成的偏差量,默認為10
*/
public int DEFAULT_DEVIATE;
/**
* 判斷是否重新繪制圖像
*/
private boolean isReSet = true;
public DouYuView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 獲取自定義屬性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DouYuView);
mUintWidth = ta.getDimensionPixelOffset(R.styleable.DouYuView_unitHeight, 0);
mUintHeight = ta.getDimensionPixelOffset(R.styleable.DouYuView_unitHeight, 0);
mUnitHeightScale = ta.getInteger(R.styleable.DouYuView_unitHeightScale, 4);
mUnitWidthScale = ta.getInteger(R.styleable.DouYuView_unitWidthScale, 5);
Drawable showBp = ta.getDrawable(R.styleable.DouYuView_unitShowSrc);
mShowBp = drawableToBitamp(showBp);
Drawable shadeBp = ta.getDrawable(R.styleable.DouYuView_unitShadeSrc);
mShadeBp = drawableToBitamp(shadeBp);
needRotate = ta.getBoolean(R.styleable.DouYuView_needRotate, true);
DEFAULT_DEVIATE = ta.getInteger(R.styleable.DouYuView_deviate, 10);
ta.recycle();
// 初始化
mPaint = new Paint();
mPaint.setAntiAlias(true);
if (needRotate) {
rotate = (int) (Math.random() * 3) * 90;
} else {
rotate = 0;
}
}
注釋寫的比較全,應該很好明白,然後我們需要獲取我們的圖片資源並且對圖片做一些處理,來使我們的圖片不會出現變形等情況.
/**
* drawable轉bitmap
*
* @param drawable
* @return
*/
private Bitmap drawableToBitamp(Drawable drawable) {
if (null == drawable) {
return null;
}
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bd = (BitmapDrawable) drawable;
return bd.getBitmap();
}
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, w, h);
drawable.draw(canvas);
return bitmap;
}
/**
* 縮放圖片
*
* @param bp
* @param x
* @param y
* @return
*/
public static Bitmap handleBitmap(Bitmap bp, float x, float y) {
int w = bp.getWidth();
int h = bp.getHeight();
float sx = (float) x / w;
float sy = (float) y / h;
Matrix matrix = new Matrix();
matrix.postScale(sx, sy);
Bitmap resizeBmp = Bitmap.createBitmap(bp, 0, 0, w,
h, matrix, true);
return resizeBmp;
}
我們需要獲取到要驗證的圖片資源,然後需要通過縮放來使圖片和控件寬高保持一致,以免導致圖片顯示異常等問題.
獲取到圖片資源之後就可以去生成陰影部分和滑塊了,我們使用兩張圖片,需要注意的是第二張圖片邊框內部分使用白色,這次用兩張星星的圖片如下:


在生成陰影部分之前,需要隨機生成一個坐標點,代碼如下:
/**
* 隨機生成生成滑塊的XY坐標
*/
private void initUnitXY() {
mUnitRandomX = (int) (Math.random() * (mBitmap.getWidth() - mUintWidth));
mUnitRandomY = (int) (Math.random() * (mBitmap.getHeight() - mUintHeight));
// 防止生成的位置距離太近
if (mUnitRandomX <= mBitmap.getWidth() / 2) {
mUnitRandomX = mUnitRandomX + mBitmap.getWidth() / 4;
}
// 防止生成的X坐標截圖時導致異常
if (mUnitRandomX + mUintWidth > getWidth()) {
initUnitXY();
return;
}
}
獲取X坐標需要注意兩點,一點是如果X太小,可能驗證時我們滑動的距離太近,效果不太好,所以進行了判斷.另外,如果我們取到的X坐標過大,會導致截取滑塊時,截取異常.非別進行了處理.
算好了坐標值,就可以生成我們的陰影圖片和滑塊了
/**
* 創建遮擋的圖片(陰影部分)
*
* @return
*/
private Bitmap drawTargetBitmap() {
// 繪制圖片
Bitmap showB;
if (null != mShowBp) {
showB = handleBitmap(mShowBp, mUintWidth, mUintHeight);
} else {
showB = handleBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.puzzle_show), mUintWidth, mUintHeight);
}
// 如果需要旋轉圖片,進行旋轉,旋轉後為了保持和滑塊大小一致,需要重新縮放比例
if (needRotate) {
showB = handleBitmap(rotateBitmap(rotate, showB), mUintWidth, mUintHeight);
}
return showB;
}
需要注意的一點就是,如果需要隨機旋轉的時候,我們要重新去算一下陰影圖片的寬高,保持和滑塊一致.
繪制帶有邊框的滑塊
/**
* 創建結合的圖片(滑塊)
*
* @param bp
*/
private Bitmap drawResultBitmap(Bitmap bp) {
// 繪制圖片
Bitmap shadeB;
if (null != mShadeBp) {
shadeB = handleBitmap(mShadeBp, mUintWidth, mUintHeight);
} else {
shadeB = handleBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.puzzle_shade), mUintWidth, mUintHeight);
}
// 如果需要旋轉圖片,進行旋轉,旋轉後為了和畫布大小保持一致,避免出現圖像顯示不全,需要重新縮放比例
if (needRotate) {
shadeB = handleBitmap(rotateBitmap(rotate, shadeB), mUintWidth, mUintHeight);
}
Bitmap resultBmp = Bitmap.createBitmap(mUintWidth, mUintHeight,
Bitmap.Config.ARGB_8888);
Paint paint = new Paint();
paint.setAntiAlias(true);
Canvas canvas = new Canvas(resultBmp);
canvas.drawBitmap(shadeB, new Rect(0, 0, mUintWidth, mUintHeight),
new Rect(0, 0, mUintWidth, mUintHeight), paint);
// 選擇混合模式
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
canvas.drawBitmap(bp, new Rect(0, 0, mUintWidth, mUintHeight),
new Rect(0, 0, mUintWidth, mUintHeight), paint);
return resultBmp;
}
同樣在旋轉完成後,需要重新去按照比例對滑塊進行縮放,不然會出現顯示不全的問題.
最後是繪制這些內容
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isReSet) {
mBitmap = getBaseBitmap();
if (0 == mUintWidth) {
mUintWidth = mBitmap.getWidth() / mUnitWidthScale;
}
if (0 == mUintHeight) {
mUintHeight = mBitmap.getHeight() / mUnitHeightScale;
}
initUnitXY();
mUnitBp = Bitmap.createBitmap(mBitmap, mUnitRandomX, mUnitRandomY, mUintWidth, mUintHeight);
}
isReSet = false;
canvas.drawBitmap(drawTargetBitmap(), mUnitRandomX, mUnitRandomY, mPaint);
canvas.drawBitmap(drawResultBitmap(mUnitBp), mUnitMoveDistance, mUnitRandomY, mPaint);
}
准備工作基本完成了,接著需要提供一些方法來完成滑塊滑動、滑塊重置、驗證等功能.
/**
* 滑塊移動距離
*
* @param distance
*/
public void setUnitMoveDistance(float distance) {
mUnitMoveDistance = distance;
// 防止滑塊滑出圖片
if (mUnitMoveDistance > mBitmap.getWidth() - mUintWidth) {
mUnitMoveDistance = mBitmap.getWidth() - mUintWidth;
}
invalidate();
}
/**
* 重置
*/
public void reSet() {
isReSet = true;
mUnitMoveDistance = 0;
if (needRotate) {
rotate = (int) (Math.random() * 3) * 90;
} else {
rotate = 0;
}
invalidate();
}
/**
* 拼圖成功的回調
**/
interface onPuzzleListener {
public void onSuccess();
public void onFail();
}
/**
* 回調
*/
private onPuzzleListener mlistener;
/**
* 驗證是否拼接成功
*/
public void testPuzzle() {
if (Math.abs(mUnitMoveDistance - mUnitRandomX) <= DEFAULT_DEVIATE) {
if (null != mlistener) {
mlistener.onSuccess();
}
} else {
if (null != mlistener) {
mlistener.onFail();
}
}
}
四、完整代碼及使用
DouYuView:
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
/**
* Created by junweiliu on 16/4/26.
*/
public class DouYuView extends ImageView {
/**
* 定義畫筆
*/
private Paint mPaint;
/**
* 驗證的圖像
*/
private Bitmap mBitmap;
/**
* 驗證滑塊的高
*/
private int mUintHeight;
/**
* 驗證滑塊的寬
*/
private int mUintWidth;
/**
* 驗證滑塊寬占用整體圖片大小的比例,默認1/5
*/
private int mUnitWidthScale;
/**
* 驗證滑塊高度占用整體圖片大小的比例,默認1/4
*/
private int mUnitHeightScale;
/**
* 隨機生成滑塊的X坐標
*/
private int mUnitRandomX;
/**
* 隨機生成滑塊的Y坐標
*/
private int mUnitRandomY;
/***
* 滑塊移動的距離
*/
private float mUnitMoveDistance = 0;
/***
* 滑塊圖像
*/
private Bitmap mUnitBp;
/**
* 驗證位置圖像
*/
private Bitmap mShowBp;
/**
* 背景陰影圖像
*/
private Bitmap mShadeBp;
/**
* 是否需要旋轉
**/
private boolean needRotate;
/**
* 旋轉的角度
*/
private int rotate;
/**
* 判斷是否完成的偏差量,默認為10
*/
public int DEFAULT_DEVIATE;
/**
* 判斷是否重新繪制圖像
*/
private boolean isReSet = true;
/**
* 拼圖成功的回調
**/
interface onPuzzleListener {
public void onSuccess();
public void onFail();
}
/**
* 回調
*/
private onPuzzleListener mlistener;
/**
* 設置回調
*
* @param listener
*/
public void setPuzzleListener(onPuzzleListener listener) {
this.mlistener = listener;
}
public DouYuView(Context context) {
this(context, null);
}
public DouYuView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DouYuView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DouYuView);
mUintWidth = ta.getDimensionPixelOffset(R.styleable.DouYuView_unitHeight, 0);
mUintHeight = ta.getDimensionPixelOffset(R.styleable.DouYuView_unitHeight, 0);
mUnitHeightScale = ta.getInteger(R.styleable.DouYuView_unitHeightScale, 4);
mUnitWidthScale = ta.getInteger(R.styleable.DouYuView_unitWidthScale, 5);
Drawable showBp = ta.getDrawable(R.styleable.DouYuView_unitShowSrc);
mShowBp = drawableToBitamp(showBp);
Drawable shadeBp = ta.getDrawable(R.styleable.DouYuView_unitShadeSrc);
mShadeBp = drawableToBitamp(shadeBp);
needRotate = ta.getBoolean(R.styleable.DouYuView_needRotate, true);
DEFAULT_DEVIATE = ta.getInteger(R.styleable.DouYuView_deviate, 10);
ta.recycle();
// 初始化
mPaint = new Paint();
mPaint.setAntiAlias(true);
if (needRotate) {
rotate = (int) (Math.random() * 3) * 90;
} else {
rotate = 0;
}
}
/**
* 隨機生成生成滑塊的XY坐標
*/
private void initUnitXY() {
mUnitRandomX = (int) (Math.random() * (mBitmap.getWidth() - mUintWidth));
mUnitRandomY = (int) (Math.random() * (mBitmap.getHeight() - mUintHeight));
// 防止生成的位置距離太近
if (mUnitRandomX <= mBitmap.getWidth() / 2) {
mUnitRandomX = mUnitRandomX + mBitmap.getWidth() / 4;
}
// 防止生成的X坐標截圖時導致異常
if (mUnitRandomX + mUintWidth > getWidth()) {
initUnitXY();
return;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isReSet) {
mBitmap = getBaseBitmap();
if (0 == mUintWidth) {
mUintWidth = mBitmap.getWidth() / mUnitWidthScale;
}
if (0 == mUintHeight) {
mUintHeight = mBitmap.getHeight() / mUnitHeightScale;
}
initUnitXY();
mUnitBp = Bitmap.createBitmap(mBitmap, mUnitRandomX, mUnitRandomY, mUintWidth, mUintHeight);
}
isReSet = false;
canvas.drawBitmap(drawTargetBitmap(), mUnitRandomX, mUnitRandomY, mPaint);
canvas.drawBitmap(drawResultBitmap(mUnitBp), mUnitMoveDistance, mUnitRandomY, mPaint);
}
/**
* 重置
*/
public void reSet() {
isReSet = true;
mUnitMoveDistance = 0;
if (needRotate) {
rotate = (int) (Math.random() * 3) * 90;
} else {
rotate = 0;
}
invalidate();
}
/**
* 獲取每次滑動的平均偏移值
*
* @return
*/
public float getAverageDistance(int max) {
return (float) (mBitmap.getWidth() - mUintWidth) / max;
}
/**
* 滑塊移動距離
*
* @param distance
*/
public void setUnitMoveDistance(float distance) {
mUnitMoveDistance = distance;
// 防止滑塊滑出圖片
if (mUnitMoveDistance > mBitmap.getWidth() - mUintWidth) {
mUnitMoveDistance = mBitmap.getWidth() - mUintWidth;
}
invalidate();
}
/**
* 驗證是否拼接成功
*/
public void testPuzzle() {
if (Math.abs(mUnitMoveDistance - mUnitRandomX) <= DEFAULT_DEVIATE) {
if (null != mlistener) {
mlistener.onSuccess();
}
} else {
if (null != mlistener) {
mlistener.onFail();
}
}
}
/**
* 創建遮擋的圖片(陰影部分)
*
* @return
*/
private Bitmap drawTargetBitmap() {
// 繪制圖片
Bitmap showB;
if (null != mShowBp) {
showB = handleBitmap(mShowBp, mUintWidth, mUintHeight);
} else {
showB = handleBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.puzzle_show), mUintWidth, mUintHeight);
}
// 如果需要旋轉圖片,進行旋轉,旋轉後為了保持和滑塊大小一致,需要重新縮放比例
if (needRotate) {
showB = handleBitmap(rotateBitmap(rotate, showB), mUintWidth, mUintHeight);
}
return showB;
}
/**
* 創建結合的圖片(滑塊)
*
* @param bp
*/
private Bitmap drawResultBitmap(Bitmap bp) {
// 繪制圖片
Bitmap shadeB;
if (null != mShadeBp) {
shadeB = handleBitmap(mShadeBp, mUintWidth, mUintHeight);
} else {
shadeB = handleBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.puzzle_shade), mUintWidth, mUintHeight);
}
// 如果需要旋轉圖片,進行旋轉,旋轉後為了和畫布大小保持一致,避免出現圖像顯示不全,需要重新縮放比例
if (needRotate) {
shadeB = handleBitmap(rotateBitmap(rotate, shadeB), mUintWidth, mUintHeight);
}
Bitmap resultBmp = Bitmap.createBitmap(mUintWidth, mUintHeight,
Bitmap.Config.ARGB_8888);
Paint paint = new Paint();
paint.setAntiAlias(true);
Canvas canvas = new Canvas(resultBmp);
canvas.drawBitmap(shadeB, new Rect(0, 0, mUintWidth, mUintHeight),
new Rect(0, 0, mUintWidth, mUintHeight), paint);
// 選擇交集去上層圖片
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
canvas.drawBitmap(bp, new Rect(0, 0, mUintWidth, mUintHeight),
new Rect(0, 0, mUintWidth, mUintHeight), paint);
return resultBmp;
}
/**
* 獲取實際顯示的圖片
*
* @return
*/
public Bitmap getBaseBitmap() {
Bitmap b = drawableToBitamp(getDrawable());
float scaleX = 1.0f;
float scaleY = 1.0f;
// 如果圖片的寬或者高與view的寬高不匹配,計算出需要縮放的比例;縮放後的圖片的寬高,一定要大於我們view的寬高;所以我們這裡取大值;
scaleX = getWidth() * 1.0f / b.getWidth();
scaleY = getHeight() * 1.0f / b.getHeight();
Matrix matrix = new Matrix();
matrix.setScale(scaleX, scaleY);
Bitmap bd = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(),
matrix, true);
return bd;
}
/**
* drawable轉bitmap
*
* @param drawable
* @return
*/
private Bitmap drawableToBitamp(Drawable drawable) {
if (null == drawable) {
return null;
}
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bd = (BitmapDrawable) drawable;
return bd.getBitmap();
}
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, w, h);
drawable.draw(canvas);
return bitmap;
}
/**
* 縮放圖片
*
* @param bp
* @param x
* @param y
* @return
*/
public static Bitmap handleBitmap(Bitmap bp, float x, float y) {
int w = bp.getWidth();
int h = bp.getHeight();
float sx = (float) x / w;
float sy = (float) y / h;
Matrix matrix = new Matrix();
matrix.postScale(sx, sy);
Bitmap resizeBmp = Bitmap.createBitmap(bp, 0, 0, w,
h, matrix, true);
return resizeBmp;
}
/**
* 旋轉圖片
*
* @param degree
* @param bitmap
* @return
*/
public Bitmap rotateBitmap(int degree, Bitmap bitmap) {
Matrix matrix = new Matrix();
matrix.postRotate(degree);
Bitmap bm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, true);
return bm;
}
}
MainActivity:
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.SeekBar;
import android.widget.Toast;
public class MainActivity extends Activity {
/**
* 滑塊
*/
private SeekBar mSeekBar;
/**
* 自定義的控件
*/
private DouYuView mDY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mDY = (DouYuView) findViewById(R.id.dy_v);
mSeekBar = (SeekBar) findViewById(R.id.sb_dy);
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
// Log.e("main", "當前位置" + i);
mDY.setUnitMoveDistance(mDY.getAverageDistance(seekBar.getMax()) * i);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mDY.testPuzzle();
}
});
mDY.setPuzzleListener(new DouYuView.onPuzzleListener() {
@Override
public void onSuccess() {
// mSeekBar.setEnabled(false);
Toast.makeText(MainActivity.this, "驗證成功", Toast.LENGTH_SHORT).show();
mSeekBar.setProgress(0);
mDY.reSet();
}
@Override
public void onFail() {
Toast.makeText(MainActivity.this, "驗證失敗", Toast.LENGTH_SHORT).show();
mSeekBar.setProgress(0);
}
});
}
}
顯示的結果圖:

Android Handler 機制實現原理分析
handler在安卓開發中是必須掌握的技術,但是很多人都是停留在使用階段。使用起來很簡單,就兩個步驟,在主線程重寫handler的handleMessage( )方法,在
android 7.0對開發者會有哪些影響
Android N 除了提供諸多新特性和功能外,還對系統和 API 行為做出了各種變更。 本文重點介紹您應該了解並在開發應用時加以考慮的一些重要變更。如果您之前發布過 A
Android OpenGL ES向導學習筆記(掃盲專用)
Android 目前支持下面幾個版本的OpenGL ES API : OpenGL ES 1.0 和 1.1 :Android 1.0和更高的版本支持這個API規范。 O
Jenkins構建Android項目持續集成之findbugs的使用
題外話這篇本來和之前的系列要一起出的,但是因為中間公司要發布一個版本,給耽擱了,今天工作做完了,又閒了下來。所以就又來繼續jenkins構建Android項目持續集成系列