Android:图形解锁的绘制

先上图:
\
 

其实很简单,不用过多解释,一点点注释就够了。

Java代码:


package com.example.graphlock;

import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar;
import android.support.v4.app.Fragment;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.os.Bundle;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.os.Build;

public class MainActivity extends ActionBarActivity implements OnTouchListener {
    private RelativeLayout relativeLayout;// 用来摆放九个圆形
    
    private ImageView view;// 用来绘制解锁路径
    
    private Path path;// 划过的路径
    
    private Paint paint;
    
    private Canvas canvas;
    
    private Dot[] array = new Dot[9];// 圆形的数组
    
    private Dot lastDot;// 上一个经过的点
    
    private Bitmap bitmap;// 绘制用的bitmap
    
    private boolean drawing = false;// 是否正在画图
    
    private int radius = 0;// 圆形半径
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 锁定竖屏
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        // 不显示标题栏
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // 全屏
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_main);
        relativeLayout = (RelativeLayout) findViewById(R.id.rela);
        view = (ImageView) findViewById(R.id.view);
        view.setOnTouchListener(this);
        drawDots();
    }
    
    /**
     * 放置九个圆形 将九个圆形在屏幕中居中放置,每屏幕的三分之一宽度为一格,横竖排各三个,每个圆宽度是屏幕宽度的1/6
     */
    protected void drawDots() {
        int TopMars = (getScreenHeight() - getScreenWidth()) / 2;
        radius = getScreenWidth() / 12;
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(radius * 2, radius * 2);
                params.leftMargin = (int) (radius * 4 * (j + 0.25));
                params.topMargin = (int) (TopMars + radius * 4 * (i + 0.25));
                // 新建半径为radius的圆形
                Dot dot = new Dot(MainActivity.this, radius);
                array[i * 3 + j] = dot;
                relativeLayout.addView(dot, params);
            }
        }
    }
    
    /**
     * 检查pointF是否在某个圆形范围内
     * 
     * @param point
     *            要检查的点
     * @return 如果确实在某个圆形范围内,则返回该圆形,反之返回null
     */
    private Dot hitValidDot(PointF point) {
        for (int i = 0; i < array.length; i++) {
            Dot dot = array[i];
            if (!dot.getPassed()) {
                int[] location = { 0, 0 };
                dot.getLocationOnScreen(location);
                if (Math.sqrt((point.x - location[0] - radius) * (point.x - location[0] - radius)
                              + (point.y - location[1] - radius)
                              * (point.y - location[1] - radius)) < radius) {
                    return dot;
                }
            }
        }
        return null;
    }
    
    /**
     * 要绘制到的目标图片上的触摸事件 本方法里view.invalidate()并不是必须的,有没有一样……
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 检查手机按下的点是否在某个圆形内,如果是则以此圆形为起点开始绘制图形
                PointF point = new PointF(event.getRawX(), event.getRawY());
                Dot dot = hitValidDot(point);
                if (dot != null) {
                    // 开始绘制 先实例化要绘制的bitmap canvas paint 和绘制的路径path
                    bitmap = Bitmap.createBitmap(getWindowWidth(), getWindowHeight(), Config.ARGB_8888);
                    canvas = new Canvas(bitmap);
                    paint = new Paint();
                    path = new Path();
                    // 获取此圆形中心点的位置
                    RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) dot.getLayoutParams();
                    PointF startPoint = new PointF(params.leftMargin + radius, params.topMargin + radius);
                    // 将loasDot赋值给dot,并将dot设置为经过状态
                    lastDot = dot;
                    lastDot.drawPassed();
                    // 将圆形的中心点设置为路径的起点 并设置要绘制路径的颜色的宽度
                    path.moveTo(startPoint.x, startPoint.y);
                    paint.setARGB(255, 0, 0, 255);
                    paint.setStrokeWidth(8);
                    paint.setStyle(Style.STROKE);
                    // 绘制到屏幕
                    view.setImageBitmap(bitmap);
                    // 标记为正在绘图中
                    drawing = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (drawing) {
                    // 先清空图片 否则看到的是每次绘制的叠加效果
                    clear();
                    // 同MotionEvent.ACTION_DOWN中一样 检查是否经过了某一点
                    PointF point2 = new PointF(event.getRawX(), event.getRawY());
                    Dot dot2 = hitValidDot(point2);
                    if (dot2 != null) {
                        // 不过有时候两点之间可能会有第三个点,如果第三个点为非经过状态,则将此点设置为经过状态
                        Dot dotBetween = checkDotBetween(lastDot, dot2);
                        if (dotBetween != null) {
                            lastDot = dotBetween;
                            lastDot.drawPassed();
                            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) dot2.getLayoutParams();
                            path.lineTo(params.leftMargin + radius, params.topMargin + radius);
                        }
                        lastDot = dot2;
                        lastDot.drawPassed();
                        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) dot2.getLayoutParams();
                        path.lineTo(params.leftMargin + radius, params.topMargin + radius);
                    }
                    // 绘制出经过的所有点的路径
                    canvas.drawPath(path, paint);
                    // 绘制出上一个点到手指触摸的位置的路径
                    RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) lastDot.getLayoutParams();
                    canvas.drawLine(params.leftMargin + radius, params.topMargin + radius, event.getX(), event.getY(), paint);
                    view.invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                if (drawing) {
                    // 手指抬起后,清空并重新绘制所有经过的点的路径,这样就会清除上一个点到手指触摸的位置的路径了
                    clear();
                    canvas.drawPath(path, paint);
                    view.invalidate();
                    // 绘制完毕,将绘制状态改为false
                    drawing = false;
                    // 三秒种后重置,放在这仅仅是为了测试重置功能
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            clearAllDrawing();
                        }
                    }, 3000);
                }
                break;
            default:
                break;
        }
        return true;
    }
    
    /**
     * 重置所有为初始状态
     */
    protected void clearAllDrawing() {
        clear();
        for (int i = 0; i < array.length; i++) {
            Dot dot = array[i];
            if (dot != null) {
                dot.drawNormal();
            }
        }
        drawing = false;
    }
    
    /**
     * 查检两点之间是否经过第三点,如果是则返回第三点,否则返回null
     */
    protected Dot checkDotBetween(Dot dot1, Dot dot2) {
        int[] loc1 = { 0, 0 };
        int[] loc2 = { 0, 0 };
        dot1.getLocationOnScreen(loc1);
        dot2.getLocationOnScreen(loc2);
        // 两点之间的中点
        PointF pointF = new PointF((loc1[0] + loc2[0]) / 2 + radius, (loc1[1] + loc2[1]) / 2 + radius);
        return hitValidDot(pointF);
    }
    
    /**
     * 清空画面
     */
    protected void clear() {
        if (canvas != null && paint != null) {
            paint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
            canvas.drawPaint(paint);
            paint.setXfermode(new PorterDuffXfermode(Mode.SRC));
            view.invalidate();
        }
    }
    
    /**
     * @return 屏幕宽度
     */
    public int getScreenWidth() {
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        return metrics.widthPixels;
    }
    
    /**
     * @return 屏幕高度
     */
    public int getScreenHeight() {
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        return metrics.heightPixels;
    }
    
    /**
     * @return 返回窗口内容的宽度,不包括通知栏的标题栏,其实跟getScreenWidth()一样
     */
    public int getWindowWidth() {
        return getWindow().findViewById(Window.ID_ANDROID_CONTENT).getWidth();
    }
    
    /**
     * @return 返回窗口内容的高度,不包括通知栏的标题栏,但是在这里是全屏,所以与getScreenHeight()返回的其实是一致的
     */
    public int getWindowHeight() {
        return getWindow().findViewById(Window.ID_ANDROID_CONTENT).getHeight();
    }
    
    /**
     * 圆形
     */
    public class Dot extends ImageView {
        private int dotradius = 0;// 圆形半径
        
        private boolean passed = false;// 是否经过的状态
        
        public Dot(Context context) {
            super(context);
        }
        
        public Dot(Context context, int rad) {
            super(context);
            dotradius = rad;
            setLayoutParams(new LayoutParams(dotradius * 2, dotradius * 2));
            drawNormal();
        }
        
        /**
         * 绘制未经过时的状态
         */
        public void drawNormal() {
            passed = false;
            Bitmap bm = Bitmap.createBitmap(dotradius * 2, dotradius * 2, Config.ARGB_8888);
            Paint paint = new Paint();
            Canvas canvas = new Canvas(bm);
            paint.setAntiAlias(true);
            paint.setARGB(255, 156, 156, 156);
            paint.setStyle(Style.STROKE);
            paint.setStrokeWidth(5);
            canvas.drawCircle(dotradius, dotradius, dotradius - paint.getStrokeWidth(), paint);
            paint.setStrokeWidth(1);
            paint.setStyle(Style.FILL_AND_STROKE);
            canvas.drawCircle(dotradius, dotradius, 3, paint);
            setImageBitmap(bm);
        }
        
        /**
         * 绘制经过时的状态
         */
        public void drawPassed() {
            passed = true;
            Bitmap bm = Bitmap.createBitmap(dotradius * 2, dotradius * 2, Config.ARGB_8888);
            Paint paint = new Paint();
            Canvas canvas = new Canvas(bm);
            paint.setAntiAlias(true);
            paint.setARGB(255, 0, 0, 255);
            paint.setStyle(Style.STROKE);
            paint.setStrokeWidth(5);
            canvas.drawCircle(dotradius, dotradius, dotradius - paint.getStrokeWidth(), paint);
            paint.setStyle(Style.FILL_AND_STROKE);
            canvas.drawCircle(dotradius, dotradius, dotradius / 3, paint);
            setImageBitmap(bm);
        }
        
        public boolean getPassed() {
            return passed;
        }
    }
}



布局xml代码,很简单:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <RelativeLayout
        android:id="@+id/rela"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值