Android自定义多宫格解锁控件

在此之前,一直在想九宫格的实现方法,经过一个上午的初步研究终于完成了一个简单的N*N的宫格解锁组件,代码略显粗糙,仅仅做到简单的实现,界面等后期在做优化,纯粹是学习的目的,在算法上有点缺陷,如果有错误或者更好的方法,欢迎提出,相互学习。先来看一下预览图

九宫格效果展示

N=3 手指抬起

N=4 手指没有抬起

其他的废话不多说了,直接开始吧.....

实现步骤

  • 设置声明属性attrs.xml文件
  • 创建SeniorPoint.java文件
  • 创建View并重写其中的几个重要方法
  • 设置触摸事件,并进行数据处理
  • 设置回调函数,在Activity里面调用

设置声明属性

很简单的xml内容,在res文件夹里面新建文件attrs.xml,将下面的内容写入即可。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Lock">
        <!--圆的半径-->
        <attr name="circleRadius" format="dimension"></attr>
        <!--圆的颜色-->
        <attr name="circleColor" format="color"></attr>
        <!--圆的线宽-->
        <attr name="circlrWidth" format="dimension"></attr>
        <!--线的宽度-->
        <attr name="LineWidth" format="dimension"></attr>
        <!--线的颜色-->
        <attr name="LineColor" format="color"></attr>
        <!--松开手之后线的颜色-->
        <attr name="LineColorAfterLeave" format="color"></attr>
        <!--圆圈的个数-->
        <attr name="circlrNumber" format="integer"></attr>
    </declare-styleable>
</resources>

创建SeniorPoint.java文件

SeniorPoint.java是一个Bean,里面保存着以圆心点的参考信息,代码如下:

package cn.example.tao.newview;

import android.graphics.Point;

/**
 * Created by Tao on 2017/2/3.
 */

public class SeniorPoint extends Point {
    private boolean isSelect=false;

    public SeniorPoint(int x, int y, boolean isSelect) {
        super(x, y);
        this.isSelect = isSelect;
    }
    public boolean isSelect() {
        return isSelect;
    }

    public void setSelect(boolean select) {
        isSelect = select;
    }
}

创建View并重写其中的几个重要方法

至于怎么自定义View这里不在过多的赘述,可以看一下我的文章,里面写了怎么自定义一个齿轮的View http://www.jianshu.com/p/104a9d7eeefd ,创建java文件Lock.java,继承View组件

获取在Activity布局中的设置属性值

在Activity的布局中的设置如下,具体每个属性的意义,请结合attrs.xml文件分析:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@mipmap/sky"
    >
    <cn.example.tao.newview.widget.Lock
        android:id="@+id/lock"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true"
        tools:circleColor="#EAEAEA"
        tools:circlrWidth="2dp"
        tools:circleRadius="30dp"
        tools:LineColor="#EAEAEA"
        tools:LineWidth="3dp"
        tools:LineColorAfterLeave="#77E6D8"
        tools:circlrNumber="3"
        />
</LinearLayout>

首先获得我们在xml文件中设置的属性值,代码如下:

    public Lock(Context context, AttributeSet attrs) throws Exception {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Lock);
        //绘制圆的半径,默认值30dp
        circleRadius = typedArray.getDimension(R.styleable.Lock_circleRadius, dp2px(30));
        //绘制圆形的颜色,默认白色
        circleColor = typedArray.getColor(R.styleable.Lock_circleColor, Color.WHITE);
        //绘制圆形的宽度,默认3dp
        circleWidth = typedArray.getDimension(R.styleable.Lock_circlrWidth, dp2px(3));
        //折线的颜色
        lineColor = typedArray.getColor(R.styleable.Lock_LineColor, Color.GRAY);
        //折线的宽度
        lineWidth = typedArray.getDimension(R.styleable.Lock_LineWidth, dp2px(1));
        //连线完成后的线的颜色
        lineColorAfterLeaver = typedArray.getColor(R.styleable.Lock_LineColorAfterLeave, Color.argb(255, 92, 186, 167));
        //每行圆的数目,有事N*N,所以也是每列的数目,当然也可以根据次设置行数和列数不同的样式
        circlrNumber=typedArray.getInt(R.styleable.Lock_circlrNumber,3);
        typedArray.recycle();
        //设置圆的数量为0或者负数的时候异常抛出
        if (circlrNumber<1)
            throw new Exception("圆的数量不能为0或负数");
        //用字符串的形式保存点的位置,比如01代表第0行1列,当然可以在回调函数根据自己的需要设计
        password=new StringBuffer();
        mPaint = new Paint();
        mPaint.setStrokeWidth(dp2px(5));
        //初始化保存圆心位置的二维数组
        location = new SeniorPoint[circlrNumber][circlrNumber];
    }

    private float dp2px(int i) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, i, getResources().getDisplayMetrics());
    }

重写测量方法

在此直接写代码,不解释了

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = getValueByComplete(widthMeasureSpec);
        int height = getValueByComplete(heightMeasureSpec);
        setMeasuredDimension(width, width);
    }

    public int getValueByComplete(int value) {
        int size = MeasureSpec.getSize(value);
        int mode = MeasureSpec.getMode(value);
        int resultValue = 0;
        if (mode == MeasureSpec.EXACTLY) {
            resultValue = size;
        } else {
            resultValue = (int) mPaint.descent();
            if (mode == MeasureSpec.AT_MOST)
                resultValue = size;
        }
        return resultValue;
    }

重写绘制方法

这里体现是对界面的绘制,主要是绘制圆和线,具体解释参考注释,在看一下这个懒到家的模型图,以N=3为参数画的,主要是注意一些点的设置

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //设置绘制圆形的时候,圆心的移动步长
        int setp_x = getWidth() / circlrNumber;
        int setp_y = getHeight() / circlrNumber;
        //设置第一个圆的位置,后面的圆形的绘制都是相对于第一各院的圆心的位置进行移动,移动的单位也就是setp_x和setp_y
        int mPaint_x = getWidth() / (2*circlrNumber), mPaint_y = getHeight() / (2*circlrNumber);
        //设置绘制圆形的画笔信息
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(circleWidth);
        mPaint.setColor(circleColor);
        //循环,开始绘制圆形
        for (int i = 0; i < circlrNumber; i++)
            for (int j = 0; j < circlrNumber; j++) {
                //此处开始绘保存圆心位置信息,设置为没有选中
                if (location[i][j] == null)
                    location[i][j] = new SeniorPoint(mPaint_x + j * setp_x, mPaint_y + i * setp_y, false);
                //开始绘制圆形,圆心坐标(mPaint_x + i * setp_x, mPaint_y + j * setp_y)
                canvas.drawCircle(mPaint_x + i * setp_x, mPaint_y + j * setp_y, circleRadius, mPaint);
            }
        //使用arrayList保存被选中的点的信息
        if (arrayList != null && arrayList.size() > 0) {
            //如果存在被选中的点,则开始进行连线操作
            //重新设置画笔的参数信息
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(lineWidth);
            //如果现在的点的位置为(0,0)那么说明,手已经抬起来了,将这显得颜色更改为设置颜色,否则的话使用另外的颜色
            if (nowPoint != null && nowPoint.x == 0 && nowPoint.y == 0)
                mPaint.setColor(lineColorAfterLeaver);
            else mPaint.setColor(lineColor);
            //进行折线的绘制工作
            for (int i = 0; i < arrayList.size(); i++) {
                canvas.drawPoint(arrayList.get(i).x, arrayList.get(i).y, mPaint);
                if (i != 0) {
                    canvas.drawLine(arrayList.get(i - 1).x, arrayList.get(i - 1).y, arrayList.get(i).x, arrayList.get(i).y, mPaint);
                }
            }
            //如果手没有抬起,继续跟随手的位置来移动
             if ((nowPoint != null && nowPoint.x != 0 && nowPoint.y != 0)) {
                canvas.drawLine(arrayList.get(arrayList.size() - 1).x, arrayList.get(arrayList.size() - 1).y, nowPoint.x, nowPoint.y, mPaint);
            }

        }

    }

设置触摸事件,并进行数据处理

    @Override
    public boolean onTouchEvent(MotionEvent event) {
//        return super.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //每次重新按下之前,要清除arratList中的列表保存的信息
                arrayList.clear();
            case MotionEvent.ACTION_MOVE:
                //判断当前的手指的位置有没有在院内,如果在院内返回这个圆的的圆心并设置改圆为选中状态,否则返回null
                SeniorPoint select = checkLocation(event.getX(), event.getY());
                //配置点前手指的位置
                if (nowPoint == null)
                    nowPoint = new SeniorPoint((int) event.getX(), (int) event.getY(), false);
                else nowPoint.set((int) event.getX(), (int) event.getY());
                if (select != null)
                    select.setSelect(true);
                //重绘
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                //手指从屏幕离开后,将当前点的坐标设置为(0,0)
                nowPoint.set(0, 0);
                //离开后,读取已经选中的位置信息,返回给回调函数
                //这里仅仅返回来的坐标点的位置,需要处理下才行
                password.delete(0,password.length());
                for (int i = 0; i < arrayList.size(); i++) {
                     password.append("第"+i+"个点的坐标 X:" + arrayList.get(i).x + "  Y:" + arrayList.get(i).y + "\n");
                }
                if (onFinsh != null)
                    onFinsh.leaver(password.toString());

                //重绘
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

    public SeniorPoint checkLocation(float x, float y) {
        //此处循环检测九个点的位置,此处代码使用算法优化,没有必要循环判断位置
        //后面有时间会专门写一个文章来分析下,追求更快的方法
        double radio = dp2px(30);
        for (int i = 0; i < circlrNumber; i++)
            for (int j = 0; j < circlrNumber; j++) {
                double l = getLong(x, y, location[i][j]);
                if (l <= radio) {
                    //如果已选中的列表的长度为0 或者长度不是0,但是也不能和arryList表中上一个值完全一样,这样才能添加
                    if (arrayList.size() ==0 || (arrayList.size()>=1 && (location[i][j].x != arrayList.get(arrayList.size() - 1).x || location[i][j].y != arrayList.get(arrayList.size() - 1).y)))
                    arrayList.add(location[i][j]);
                    return location[i][j];
                }
            }
        return null;
    }
    public double getLong(float x, float y, SeniorPoint point) {
        //返回点前手指的点到圆形的位置
        double s = Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2);
        return Math.sqrt(s);
    }

设置回调函数,在Activity里面调用

手抬起的时候,应该将选择的结果返回给Activity,在Activity中检查是否解锁成功,然后进行相应的处理.

首先定义接口,并在Lock.java文件中定义

private OnFinsh onFinsh;
//set方法
    public void setOnFinsh(OnFinsh onFinsh) {
        this.onFinsh = onFinsh;
    }

//当手指抬起的时候,调用其leaver()方法,将结果回调到Activity中
//在上面的OnTouch事件中调用
 if (onFinsh != null)
                    onFinsh.leaver(password.toString());

    public interface OnFinsh {
        void leaver(String password);
    }

在Activity中这样使用

package cn.example.tao.newview;

import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import cn.example.tao.newview.widget.Lock;
public class MainActivity extends FragmentActivity {
    //声明变量
    private Lock lock;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        //绑定变量,并设置回调函数
        lock= (Lock) findViewById(R.id.lock);
        lock.setOnFinsh(new Lock.OnFinsh() {
            @Override
            public void leaver(String password) {
                Log.e("PassWord",password);
            }
        });
    }
}

打印的结果如下:

N=4的选中10个点

后记

当然也可以设置一下属性:

  • 是否可见,设置为布尔型数据,如果true则绘制直线,否则不绘制直线。
  • 增加选中一个点之后即调用的回调函数
  • 设置类型为填充圆或者点或者图片

    ......

目前还存在的问题:

  • 当手指处于某一点的时候,判断这个位置是不是在某一圆内,这里为了简单,使用了循环判断的方法,但是显然这种效率是很慢的,所以我想了下面的过程,不知是否合适:

    1、将view界面想象分割成N*N的界面

    2、首先大致判断手指的位置是不是在某个方格内,如果在,那么找到这个方格内的那个圆

    3、通过一些逻辑计算得到这个圆的圆心位置

本博客内容一致同步到本人的博客站点:http://www.zhoutaotao.xyz 欢迎访问留言交流

原文地址:https://www.cnblogs.com/zhoutao825638/p/10382012.html

时间: 2024-12-28 20:35:09

Android自定义多宫格解锁控件的相关文章

Android 自定义View之自绘控件

首先要提前声明一下,我对于自定义View的理解并不是很深,最近啃了几天guolin博主写的关于自定义View的博客,讲的非常棒,只不过涉及到源码和底层的一些东西,我自己就懵逼了,目前只是会了关于自定义View的简单使用,不过还是要写出来,当做练习了,哈哈~对于一些没有接触过的初学者,希望会有所帮助,共同成长: 按类型,自定义View可以分为三种:自绘控件.组合控件.继承控件,对于这三种类型,我会写三篇博客来分别介绍和使用. 自定义View中有三个非常重要的方法,分别为: onMeasure():

android 九宫格(16宫格)控件

public class NineRectView extends ViewGroup { private Context ctx; private int wSize,hSize,row,column,count,childWidth,childHeight,parent_padding_top_bottom;//布局方式按照默认wSize=Hsize //间距都相同 private int childMargin =4; private int parent_padding; private

【Android】Android自定义带board的圆角控件

介绍 圆角控件常用于头像,按钮,图标等,用途十分广泛,而且常常配合board使用. 在IOS中,UIVIew的CALayer层已经提供了圆角和board的方法,所以圆角控件的制作非常简单,只需要类似以下简单代码即可实现: view.layer.cornerRadius = 20; view.layer.borderColor = [UIColor yellowColor].CGColor; view.layer.borderWidth = 10; view.clipsToBounds = YES

WPF自定义控件之图形解锁控件 ScreenUnLock

ScreenUnLock 与智能手机上的图案解锁功能一样.通过绘制图形达到解锁或记忆图形的目的. 本人突发奇想,把手机上的图形解锁功能移植到WPF中.也应用到了公司的项目中. 在创建ScreenUnLock之前,先来分析一下图形解锁的实现思路. 1.创建九宫格原点(或更多格子),每个点定义一个坐标值 2.提供图形解锁相关扩展属性和事件,方便调用者定义.比如:点和线的颜色(Color),操作模式(Check|Remember),验证正确的颜色(RightColor), 验证失败的颜色(ErrorC

ANDROID L——Material Design详解(UI控件)

转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! Android L: Google已经确认Android L就是Android Lollipop(5.0). 前几天发现Android5.0正式版的sdk已经可以下载了,而且首次搭载Android L系统的Nexus 6和 Nexus 9也即将上市. 所以是时候开始学习Android L了! 关于Android L如何配置模拟器和创建项目,如果大家有兴趣的话可以看看我之前的一篇文章: A

Android自定义View(RollWeekView-炫酷的星期日期选择控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53420889 本文出自:[openXu的博客] 目录: 1分析 2定义控件布局 3定义CustomWeekView 4重写onMeasure 5点击后执行动画 7重置预备控件 源码下载 ??最近收到一个自定义控件的需求,需要做一个日期选择控件,实现图如下: ???? ??一次展示一个星期的5天,中间放大的为当前选中的:如果点击了其中一个日期,比如星期五,那么整体向左滑动,并将星期五慢慢放大

android - 自定义(组合)控件 + 自定义控件外观

转载:http://www.cnblogs.com/bill-joy/archive/2012/04/26/2471831.html android - 自定义(组合)控件 + 自定义控件外观 Android自定义View实现很简单 继承View,重写构造函数.onDraw,(onMeasure)等函数. 如果自定义的View需要有自定义的属性,需要在values下建立attrs.xml.在其中定义你的属性. 在使用到自定义View的xml布局文件中需要加入xmlns:前缀="http://sc

【转】ANDROID自定义视图——onLayout源码 流程 思路详解

转载(http://blog.csdn.net/a396901990) 简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量——onMeasure():决定View的大小 2.布局——onLayout():决定View在ViewGroup中的位置 3.绘制——onDraw():如何绘制这个View. 而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 第一步的测量,可以参考我之前的文章:(ANDROID自定义视图——onMea

Android 自定义 View 详解

View 的绘制系列文章: Android View 绘制流程之 DecorView 与 ViewRootImpl Android View 的绘制流程之 Measure 过程详解 (一) Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 的事件分发原理解析 对于 Android 开发者来说,原生控件往往无法满足要求,需要开发者自定义一些控件,因此,需要去了解自定义 view 的实现原理.这样即使碰到需要自定义控件的时候,也可以游刃有