Android自定義View實(shí)現(xiàn)數(shù)字密碼鎖
最近項(xiàng)目上用到一個(gè)密碼加鎖功能,需要一個(gè)數(shù)字密碼界面,就想著封裝成一個(gè)View來(lái)方便管理和使用。
廢話(huà)不多說(shuō),先上最終效果圖:

思路
整體可分為2個(gè)部分來(lái)實(shí)現(xiàn),1.頂部是4個(gè)密碼位的填充;2.數(shù)字鍵盤(pán)部分。整體可以是一個(gè)縱向LinearLayout,4個(gè)密碼位用橫向LinearLayout即可,鍵盤(pán)由于是宮格形式,因此可用GridLayout來(lái)布局。由于密碼位和鍵盤(pán)數(shù)字都是以圓圈為背景,這里采用自定義一個(gè)圓形背景ImageView來(lái)使用。
實(shí)現(xiàn)
1.頁(yè)面布局
首先定義一個(gè)圓形背景的ImageView,由于最終實(shí)現(xiàn)的效果是點(diǎn)擊的時(shí)候要填充圓背景,非點(diǎn)擊狀態(tài)下是空心圓,因此可通過(guò)改變Paint的style來(lái)動(dòng)態(tài)更改顯示:
/**
* 圓形背景ImageView(設(shè)置實(shí)心或空心)
*/
public class CircleImageView extends ImageView{
private Paint mPaint;
private int mWidth;
private int mHeight;
public CircleImageView(Context context) {
this(context, null);
}
public CircleImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
public void initView(Context context){
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mPanelColor);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setAntiAlias(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
@Override
public void draw(Canvas canvas) {
canvas.drawCircle(mWidth/2, mHeight/2, mWidth/2 - 6, mPaint);
super.draw(canvas);
}
/**
* 設(shè)置圓為實(shí)心狀態(tài)
*/
public void setFillCircle(){
mPaint.setStyle(Paint.Style.FILL);
invalidate();
}
/**
* 設(shè)置圓為空心狀態(tài)
*/
public void setStrokeCircle(){
mPaint.setStyle(Paint.Style.STROKE);
invalidate();
}
}
可以看到,在onDraw中繪制了一個(gè)圓,默認(rèn)為空心狀態(tài),定義setFillCircle和setStrokeCircle這兩個(gè)方法以便外界可以方便地切換圓為實(shí)心或者空心。
圓形ImageView定義好了,開(kāi)始添加密碼位,布局如下:
inputResultView = new LinearLayout(context);
for(int i=0; i<4; i++){
CircleImageView mResultItem = new CircleImageView(context);
mResultIvList.add(mResultItem);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mResultIvRadius, mResultIvRadius);
params.leftMargin = dip2px(context, 4);
params.rightMargin = dip2px(context, 4);
mResultItem.setPadding(dip2px(context, 2),dip2px(context, 2),dip2px(context, 2),dip2px(context, 2));
mResultItem.setLayoutParams(params);
inputResultView.addView(mResultItem);
}
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
params.bottomMargin = dip2px(context, 34);
inputResultView.setLayoutParams(params);
addView(inputResultView);
接著添加數(shù)字鍵盤(pán)部分的布局:
GridLayout numContainer = new GridLayout(context);
numContainer.setColumnCount(3);
for(int i=0; i<numArr.length; i++){
RelativeLayout numItem = new RelativeLayout(context);
numItem.setPadding(mPaddingLeftRight,mPaddingTopBottom,mPaddingLeftRight,mPaddingTopBottom);
RelativeLayout.LayoutParams gridItemParams = new RelativeLayout.LayoutParams(mNumRadius, mNumRadius);
gridItemParams.addRule(CENTER_IN_PARENT);
final TextView numTv = new TextView(context);
numTv.setText(numArr[i]);
numTv.setTextColor(mPanelColor);
numTv.setTextSize(30);
numTv.setGravity(Gravity.CENTER);
numTv.setLayoutParams(gridItemParams);
final CircleImageView numBgIv = new CircleImageView(context);
numBgIv.setLayoutParams(gridItemParams);
numItem.addView(numBgIv);
numItem.addView(numTv);
numContainer.addView(numItem);
if(i == 9){
numItem.setVisibility(INVISIBLE);
}
}
//刪除按鈕
RelativeLayout deleteItem = new RelativeLayout(context);
deleteItem.setPadding(mPaddingLeftRight,mPaddingTopBottom,mPaddingLeftRight,mPaddingTopBottom);
RelativeLayout.LayoutParams gridItemParams = new RelativeLayout.LayoutParams(mNumRadius, mNumRadius);
gridItemParams.addRule(CENTER_IN_PARENT);
//假如刪除按鈕是設(shè)置自定義圖片資源的話(huà),可用注釋這段
//ImageView deleteIv = new ImageView(context);
//deleteIv.setImageResource(R.drawable.icn_delete_pw);
//deleteIv.setLayoutParams(gridItemParams);
//deleteItem.addView(deleteIv);
TextView deleteTv = new TextView(context);
deleteTv.setText("Delete");
deleteTv.setTextColor(mPanelColor);
deleteTv.setTextSize(dip2px(context, 8));
deleteTv.setLayoutParams(gridItemParams);
deleteTv.setGravity(Gravity.CENTER);
deleteItem.addView(deleteTv);
numContainer.addView(deleteItem);
LinearLayout.LayoutParams gridParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
gridParams.gravity = Gravity.CENTER_HORIZONTAL;
numContainer.setLayoutParams(gridParams);
addView(numContainer);
數(shù)字鍵盤(pán)這里用一個(gè)數(shù)組存數(shù)字內(nèi)容,遍歷添加,注意此處由于第10個(gè)的子View的時(shí)候是空白的,所以當(dāng)遍歷到第10個(gè)元素的時(shí)候,可以將其隱藏。遍歷完后再單獨(dú)添加刪除按鈕。
2.輸入邏輯
頁(yè)面布局完成了,接下來(lái)就是密碼輸入的邏輯部分,最終的效果是每點(diǎn)擊一次數(shù)字,密碼位就填充一個(gè),每點(diǎn)擊刪除按鈕一次,密碼位就回退一個(gè),輸入4個(gè)數(shù)字之后,即完成輸入,獲取結(jié)果,并重置密碼位。這里用一個(gè)StringBuilder變量來(lái)記錄當(dāng)前已輸入的密碼,每次添加就append進(jìn)去,每次刪除就調(diào)用deleteCharAt。
由于點(diǎn)擊數(shù)字按下的時(shí)候填充,松開(kāi)的時(shí)候?yàn)榭招臓顟B(tài),所以可以在ACTION_DOWN和ACTION_UP事件中分別操作:
numTv.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
numBgIv.setFillCircle();
numTv.setTextColor(Color.WHITE);
if(mPassWord.length() < 4){
mPassWord.append(numTv.getText());
mResultIvList.get(mPassWord.length()-1).setFillCircle();
if(mInputListener!=null && mPassWord.length() == 4){
//已完整輸入4個(gè)
}
}
break;
case MotionEvent.ACTION_UP:
numBgIv.setStrokeCircle();
numTv.setTextColor(mPanelColor);
break;
}
return true;
}
});
每次點(diǎn)擊的時(shí)候,判斷當(dāng)前已輸入的密碼位是否已經(jīng)超過(guò)4位,如果沒(méi)超過(guò),就繼續(xù)追加。如果等于4,就說(shuō)明輸入完成,此時(shí)的mPassWord的內(nèi)容就是最終的密碼,可以用一個(gè)接口將其回調(diào)出去方便Activity中獲取輸入的密碼:
/**
* 監(jiān)聽(tīng)輸入完畢的接口
*/
private InputListener mInputListener;
public void setInputListener(InputListener mInputListener) {
his.mInputListener = mInputListener;
}
public interface InputListener{
void inputFinish(String result);
}
然后在上面的ACTION_DOWN中輸入數(shù)字等于4的時(shí)候,回調(diào)該接口:
if(mInputListener!=null && mPassWord.length() == 4){
mInputListener.inputFinish(mPassWord.toString());
}
另外,刪除的操作單獨(dú)封裝為一個(gè)方法:
/**
* 刪除
*/
public void delete(){
if(mPassWord.length() == 0){
return;
}
mResultIvList.get(mPassWord.length()-1).setStrokeCircle();
mPassWord.deleteCharAt(mPassWord.length()-1);
}
注意點(diǎn):當(dāng)前無(wú)輸入密碼時(shí),直接return不作任何操作,假如已有輸入數(shù)字,就刪除最尾部的那個(gè)數(shù)字。
最后,還要考慮一種情況,即用戶(hù)輸入密碼錯(cuò)誤時(shí)的一些反饋,參照平時(shí)的習(xí)慣,一般是4個(gè)密碼位左右擺動(dòng)并且手機(jī)震動(dòng)效果,震動(dòng)結(jié)束之后,當(dāng)前存儲(chǔ)的密碼位重置為初始狀態(tài),如下:
/**
* 輸入錯(cuò)誤的狀態(tài)顯示(包括震動(dòng),密碼位左右搖擺效果,重置密碼位)
*/
public void showErrorStatus(){
mVibrator.vibrate(new long[]{100,100,100,100},-1);
List<Animator> animators = new ArrayList<>();
ObjectAnimator translationXAnim = ObjectAnimator.ofFloat(inputResultView, "translationX", -50.0f,50.0f,-50.0f,0.0f);
translationXAnim.setDuration(400);
animators.add(translationXAnim);
AnimatorSet btnSexAnimatorSet = new AnimatorSet();
btnSexAnimatorSet.playTogether(animators);
btnSexAnimatorSet.start();
btnSexAnimatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
resetResult();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
可以看到,在onAnimationEnd中調(diào)用了resetResult,即動(dòng)畫(huà)結(jié)束時(shí)重置密碼,resetResult方法如下:
/**
* 重置密碼輸入
*/
public void resetResult(){
for(int i=0; i<mResultIvList.size(); i++){
mResultIvList.get(i).setStrokeCircle();
}
mPassWord.delete(0, 4);
}
遍歷所有密碼位View設(shè)置為空心,并且刪除當(dāng)前mPassWord變量存儲(chǔ)的所有內(nèi)容。
完整代碼
完整的自定義數(shù)字密碼鎖代碼如下:
package com.example.zjyang.viewtest.view;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Service;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import static android.widget.RelativeLayout.CENTER_HORIZONTAL;
import static android.widget.RelativeLayout.CENTER_IN_PARENT;
/**
* Created by IT_ZJYANG on 2018/1/22.
* 數(shù)字解鎖鍵盤(pán)View
*/
public class NumLockPanel extends LinearLayout {
private String[] numArr = new String[]{"1","2","3","4","5","6","7","8","9", "", "0"};
private int mPaddingLeftRight;
private int mPaddingTopBottom;
//4個(gè)密碼位ImageView
private ArrayList<CircleImageView> mResultIvList;
private LinearLayout inputResultView;
//存儲(chǔ)當(dāng)前輸入內(nèi)容
private StringBuilder mPassWord;
//振動(dòng)效果
private Vibrator mVibrator;
//整個(gè)鍵盤(pán)的顏色
private int mPanelColor;
//4個(gè)密碼位的寬度
private int mResultIvRadius;
//數(shù)字鍵盤(pán)的每個(gè)圓的寬度
private int mNumRadius;
//每個(gè)圓的邊界寬度
private int mStrokeWidth;
public NumLockPanel(Context context) {
this(context, null);
}
public NumLockPanel(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NumLockPanel(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaddingLeftRight = dip2px(context, 21);
mPaddingTopBottom = dip2px(context, 10);
mPanelColor = Color.BLACK; //顏色代碼可采用Color.parse("#000000");
mResultIvRadius = dip2px(context, 20);
mNumRadius = dip2px(context, 66);
mStrokeWidth = dip2px(context, 2);
mVibrator = (Vibrator)context.getSystemService(Service.VIBRATOR_SERVICE);
mResultIvList = new ArrayList<>();
mPassWord = new StringBuilder();
setOrientation(VERTICAL);
setGravity(CENTER_HORIZONTAL);
initView(context);
}
public void initView(Context context){
//4個(gè)結(jié)果號(hào)碼
inputResultView = new LinearLayout(context);
for(int i=0; i<4; i++){
CircleImageView mResultItem = new CircleImageView(context);
mResultIvList.add(mResultItem);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mResultIvRadius, mResultIvRadius);
params.leftMargin = dip2px(context, 4);
params.rightMargin = dip2px(context, 4);
mResultItem.setPadding(dip2px(context, 2),dip2px(context, 2),dip2px(context, 2),dip2px(context, 2));
mResultItem.setLayoutParams(params);
inputResultView.addView(mResultItem);
}
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
params.bottomMargin = dip2px(context, 34);
inputResultView.setLayoutParams(params);
addView(inputResultView);
//數(shù)字鍵盤(pán)
GridLayout numContainer = new GridLayout(context);
numContainer.setColumnCount(3);
for(int i=0; i<numArr.length; i++){
RelativeLayout numItem = new RelativeLayout(context);
numItem.setPadding(mPaddingLeftRight,mPaddingTopBottom,mPaddingLeftRight,mPaddingTopBottom);
RelativeLayout.LayoutParams gridItemParams = new RelativeLayout.LayoutParams(mNumRadius, mNumRadius);
gridItemParams.addRule(CENTER_IN_PARENT);
final TextView numTv = new TextView(context);
numTv.setText(numArr[i]);
numTv.setTextColor(mPanelColor);
numTv.setTextSize(30);
numTv.setGravity(Gravity.CENTER);
numTv.setLayoutParams(gridItemParams);
final CircleImageView numBgIv = new CircleImageView(context);
numBgIv.setLayoutParams(gridItemParams);
numTv.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
numBgIv.setFillCircle();
numTv.setTextColor(Color.WHITE);
if(mPassWord.length() < 4){
mPassWord.append(numTv.getText());
mResultIvList.get(mPassWord.length()-1).setFillCircle();
if(mInputListener!=null && mPassWord.length() == 4){
mInputListener.inputFinish(mPassWord.toString());
}
}
break;
case MotionEvent.ACTION_UP:
numBgIv.setStrokeCircle();
numTv.setTextColor(mPanelColor);
break;
}
return true;
}
});
numItem.addView(numBgIv);
numItem.addView(numTv);
numContainer.addView(numItem);
if(i == 9){
numItem.setVisibility(INVISIBLE);
}
}
//刪除按鈕
RelativeLayout deleteItem = new RelativeLayout(context);
deleteItem.setPadding(mPaddingLeftRight,mPaddingTopBottom,mPaddingLeftRight,mPaddingTopBottom);
RelativeLayout.LayoutParams gridItemParams = new RelativeLayout.LayoutParams(mNumRadius, mNumRadius);
gridItemParams.addRule(CENTER_IN_PARENT);
//假如刪除按鈕是設(shè)置自定義圖片資源的話(huà),可用注釋這段
//ImageView deleteIv = new ImageView(context);
//deleteIv.setImageResource(R.drawable.icn_delete_pw);
//deleteIv.setLayoutParams(gridItemParams);
//deleteItem.addView(deleteIv);
TextView deleteTv = new TextView(context);
deleteTv.setText("Delete");
deleteTv.setTextColor(mPanelColor);
deleteTv.setTextSize(dip2px(context, 8));
deleteTv.setLayoutParams(gridItemParams);
deleteTv.setGravity(Gravity.CENTER);
deleteItem.addView(deleteTv);
numContainer.addView(deleteItem);
deleteTv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
delete();
}
});
LinearLayout.LayoutParams gridParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
gridParams.gravity = Gravity.CENTER_HORIZONTAL;
numContainer.setLayoutParams(gridParams);
addView(numContainer);
}
/**
* 輸入錯(cuò)誤的狀態(tài)顯示(包括震動(dòng),密碼位左右搖擺效果,重置密碼位)
*/
public void showErrorStatus(){
mVibrator.vibrate(new long[]{100,100,100,100},-1);
List<Animator> animators = new ArrayList<>();
ObjectAnimator translationXAnim = ObjectAnimator.ofFloat(inputResultView, "translationX", -50.0f,50.0f,-50.0f,0.0f);
translationXAnim.setDuration(400);
animators.add(translationXAnim);
AnimatorSet btnSexAnimatorSet = new AnimatorSet();
btnSexAnimatorSet.playTogether(animators);
btnSexAnimatorSet.start();
btnSexAnimatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
resetResult();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
/**
* 刪除
*/
public void delete(){
if(mPassWord.length() == 0){
return;
}
mResultIvList.get(mPassWord.length()-1).setStrokeCircle();
mPassWord.deleteCharAt(mPassWord.length()-1);
}
/**
* 重置密碼輸入
*/
public void resetResult(){
for(int i=0; i<mResultIvList.size(); i++){
mResultIvList.get(i).setStrokeCircle();
}
mPassWord.delete(0, 4);
}
/**
* 監(jiān)聽(tīng)輸入完畢的接口
*/
private InputListener mInputListener;
public void setInputListener(InputListener mInputListener) {
this.mInputListener = mInputListener;
}
public interface InputListener{
void inputFinish(String result);
}
/**
* dip/dp轉(zhuǎn)像素
*
* @param dipValue
* dip或 dp大小
* @return 像素值
*/
public static int dip2px(Context context, float dipValue) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (dipValue * (metrics.density) + 0.5f);
}
/**
* 圓形背景ImageView(設(shè)置實(shí)心或空心)
*/
public class CircleImageView extends ImageView{
private Paint mPaint;
private int mWidth;
private int mHeight;
public CircleImageView(Context context) {
this(context, null);
}
public CircleImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
public void initView(Context context){
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mPanelColor);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setAntiAlias(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
@Override
public void draw(Canvas canvas) {
canvas.drawCircle(mWidth/2, mHeight/2, mWidth/2 - 6, mPaint);
super.draw(canvas);
}
/**
* 設(shè)置圓為實(shí)心狀態(tài)
*/
public void setFillCircle(){
mPaint.setStyle(Paint.Style.FILL);
invalidate();
}
/**
* 設(shè)置圓為空心狀態(tài)
*/
public void setStrokeCircle(){
mPaint.setStyle(Paint.Style.STROKE);
invalidate();
}
}
}
使用
在Activity的布局文件中:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
tools:context="com.example.zjyang.viewtest.MainActivity">
<com.example.zjyang.viewtest.view.NumLockPanel
android:id="@+id/num_lock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp">
</com.example.zjyang.viewtest.view.NumLockPanel>
</RelativeLayout>
在代碼中監(jiān)聽(tīng)輸入的密碼結(jié)果:
public class MainActivity extends AppCompatActivity {
private NumLockPanel mNumLockPanel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNumLockPanel = (NumLockPanel) findViewById(R.id.num_lock);
mNumLockPanel.setInputListener(new NumLockPanel.InputListener() {
@Override
public void inputFinish(String result) {
//此處result即為輸入結(jié)果
Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
//錯(cuò)誤效果示例
mNumLockPanel.showErrorStatus();
}
});
}
}
最后,在自定義View構(gòu)造方法中初始化了圓圓和數(shù)字的顏色風(fēng)格,以及空心圓的邊界粗細(xì)大小,可根據(jù)需求自行更改。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
通過(guò)Html網(wǎng)頁(yè)調(diào)用本地安卓(android)app程序代碼
如何使用html網(wǎng)頁(yè)和本地app進(jìn)行傳遞數(shù)據(jù)呢?經(jīng)過(guò)研究,發(fā)現(xiàn)還是有方法的,總結(jié)了一下,大致有一下幾種方式2013-11-11
Mac 下 Android Studio 不打印日志的解決辦法
這篇文章主要介紹了Mac 下 Android Studio 不打印日志的解決辦法的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-10-10
Android編程ViewPager回彈效果實(shí)例分析
這篇文章主要介紹了Android編程ViewPager回彈效果,以實(shí)例形式較為詳細(xì)的分析了ViewPager回彈效果的相關(guān)使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
Android開(kāi)發(fā)實(shí)現(xiàn)切換主題及換膚功能示例
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)切換主題及換膚功能,涉及Android界面布局與樣式動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-03-03
Android開(kāi)發(fā)中日期工具類(lèi)DateUtil完整實(shí)例
這篇文章主要介紹了Android開(kāi)發(fā)中日期工具類(lèi)DateUtil,結(jié)合完整實(shí)例形式分析了Android針對(duì)日期與時(shí)間的計(jì)算、轉(zhuǎn)換、格式化、獲取等相關(guān)操作技巧,需要的朋友可以參考下2017-11-11
Android App將數(shù)據(jù)寫(xiě)入內(nèi)部存儲(chǔ)和外部存儲(chǔ)的示例
這篇文章主要介紹了Android App將數(shù)據(jù)寫(xiě)入內(nèi)部存儲(chǔ)和外部存儲(chǔ)的示例,使用外部存儲(chǔ)即訪(fǎng)問(wèn)并寫(xiě)入SD卡,需要的朋友可以參考下2016-03-03
Android進(jìn)階教程之ViewGroup自定義布局
這篇文章主要給大家介紹了關(guān)于Android進(jìn)階教程之ViewGroup自定義布局的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
Android添加ButterKnife時(shí)報(bào)錯(cuò)Error:(2, 0) Cannot add extension wit
今天小編就為大家分享一篇關(guān)于Android添加ButterKnife時(shí)報(bào)錯(cuò)Error:(2, 0) Cannot add extension with name 'android'的解決辦法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12

