热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android自定义View实现数字密码锁

这篇文章主要为大家详细介绍了Android自定义View实现数字密码锁,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

最近项目上用到一个密码加锁功能,需要一个数字密码界面,就想着封装成一个View来方便管理和使用。

废话不多说,先上最终效果图:

思路

整体可分为2个部分来实现,1.顶部是4个密码位的填充;2.数字键盘部分。整体可以是一个纵向LinearLayout,4个密码位用横向LinearLayout即可,键盘由于是宫格形式,因此可用GridLayout来布局。由于密码位和键盘数字都是以圆圈为背景,这里采用自定义一个圆形背景ImageView来使用。

实现

1.页面布局

首先定义一个圆形背景的ImageView,由于最终实现的效果是点击的时候要填充圆背景,非点击状态下是空心圆,因此可通过改变Paint的style来动态更改显示:

/** 
 * 圆形背景ImageView(设置实心或空心) 
 */ 
 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); 
   } 
 
   /** 
   * 设置圆为实心状态 
   */ 
   public void setFillCircle(){ 
     mPaint.setStyle(Paint.Style.FILL); 
     invalidate(); 
   } 
 
   /** 
   * 设置圆为空心状态 
   */ 
   public void setStrokeCircle(){ 
     mPaint.setStyle(Paint.Style.STROKE); 
     invalidate(); 
   } 
 } 

可以看到,在onDraw中绘制了一个圆,默认为空心状态,定义setFillCircle和setStrokeCircle这两个方法以便外界可以方便地切换圆为实心或者空心。

圆形ImageView定义好了,开始添加密码位,布局如下:

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); 

接着添加数字键盘部分的布局:

GridLayout numCOntainer= new GridLayout(context); 
numContainer.setColumnCount(3); 
for(int i=0; i

数字键盘这里用一个数组存数字内容,遍历添加,注意此处由于第10个的子View的时候是空白的,所以当遍历到第10个元素的时候,可以将其隐藏。遍历完后再单独添加删除按钮。

2.输入逻辑

页面布局完成了,接下来就是密码输入的逻辑部分,最终的效果是每点击一次数字,密码位就填充一个,每点击删除按钮一次,密码位就回退一个,输入4个数字之后,即完成输入,获取结果,并重置密码位。这里用一个StringBuilder变量来记录当前已输入的密码,每次添加就append进去,每次删除就调用deleteCharAt。

由于点击数字按下的时候填充,松开的时候为空心状态,所以可以在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个 
            } 
          } 
          break; 
        case MotionEvent.ACTION_UP: 
          numBgIv.setStrokeCircle(); 
          numTv.setTextColor(mPanelColor); 
          break; 
      } 
      return true; 
    } 
 }); 

每次点击的时候,判断当前已输入的密码位是否已经超过4位,如果没超过,就继续追加。如果等于4,就说明输入完成,此时的mPassWord的内容就是最终的密码,可以用一个接口将其回调出去方便Activity中获取输入的密码:

/** 
 * 监听输入完毕的接口 
 */ 
private InputListener mInputListener; 
 
public void setInputListener(InputListener mInputListener) { 
  his.mInputListener = mInputListener; 
} 
 
public interface InputListener{ 
  void inputFinish(String result); 
} 

然后在上面的ACTION_DOWN中输入数字等于4的时候,回调该接口:

if(mInputListener!=null && mPassWord.length() == 4){ 
   mInputListener.inputFinish(mPassWord.toString()); 
} 

另外,删除的操作单独封装为一个方法:

/** 
 * 删除 
 */ 
public void delete(){ 
  if(mPassWord.length() == 0){ 
    return; 
  } 
  mResultIvList.get(mPassWord.length()-1).setStrokeCircle(); 
  mPassWord.deleteCharAt(mPassWord.length()-1); 
} 

注意点:当前无输入密码时,直接return不作任何操作,假如已有输入数字,就删除最尾部的那个数字。

最后,还要考虑一种情况,即用户输入密码错误时的一些反馈,参照平时的习惯,一般是4个密码位左右摆动并且手机震动效果,震动结束之后,当前存储的密码位重置为初始状态,如下:

/** 
 * 输入错误的状态显示(包括震动,密码位左右摇摆效果,重置密码位) 
 */ 
public void showErrorStatus(){ 
  mVibrator.vibrate(new long[]{100,100,100,100},-1); 
  List 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中调用了resetResult,即动画结束时重置密码,resetResult方法如下:

/** 
 * 重置密码输入 
 */ 
public void resetResult(){ 
  for(int i=0; i

遍历所有密码位View设置为空心,并且删除当前mPassWord变量存储的所有内容。

完整代码

完整的自定义数字密码锁代码如下:

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. 
 * 数字解锁键盘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个密码位ImageView 
  private ArrayList mResultIvList; 
 
  private LinearLayout inputResultView; 
  //存储当前输入内容 
  private StringBuilder mPassWord; 
  //振动效果 
  private Vibrator mVibrator; 
  //整个键盘的颜色 
  private int mPanelColor; 
  //4个密码位的宽度 
  private int mResultIvRadius; 
  //数字键盘的每个圆的宽度 
  private int mNumRadius; 
  //每个圆的边界宽度 
  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个结果号码 
    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); 
 
    //数字键盘 
    GridLayout numCOntainer= new GridLayout(context); 
    numContainer.setColumnCount(3); 
    for(int i=0; i 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

使用

在Activity的布局文件中:

<&#63;xml version="1.0" encoding="utf-8"&#63;> 
 
 
   
 
   
 

在代码中监听输入的密码结果:

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即为输入结果 
        Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show(); 
        //错误效果示例 
        mNumLockPanel.showErrorStatus(); 
      } 
    }); 
  } 
} 

最后,在自定义View构造方法中初始化了圆圆和数字的颜色风格,以及空心圆的边界粗细大小,可根据需求自行更改。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • HDFS2.x新特性
    一、集群间数据拷贝scp实现两个远程主机之间的文件复制scp-rhello.txtroothadoop103:useratguiguhello.txt推pushscp-rr ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 本文介绍了在使用MSXML解析XML文件时出现DTD禁用问题的解决方案。通过代码示例和错误信息获取方法,解释了默认情况下DTD是禁用的,以及如何启用DTD的方法。此外,还提到了网上关于该问题的信息相对较少,因此本文提供了解决方案以供参考。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
author-avatar
手机用户2602886817
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有