仿QQ6.1手势锁

最后更新于:2022-04-01 19:52:18

项目地址:[https://github.com/103style/QQ6.1GestureLock](https://github.com/103style/QQ6.1GestureLock) 该项目是仿照当前版本的QQ手势锁,主要实现设置手势锁和检验手势锁的功能。 废话不多说 先上效果图。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-23_56cbd2dc5f8d9.jpg) **主界面代码:** ~~~ public class MainActivity extends AppCompatActivity implements View.OnClickListener{     public static String password;     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         ViewUtils.inject(this);     }     @OnClick({R.id.bt, R.id.btCheck})     @Override     public void onClick(View v) {         switch (v.getId()){             case R.id.bt:                 startActivity(new Intent(MainActivity.this, SetGestureLockActivity.class));                 break;             case R.id.btCheck:                 if (password == null){                     Toast.makeText(MainActivity.this,"请先设置手势锁",Toast.LENGTH_SHORT).show();                 }else{                     Intent intent = new Intent(MainActivity.this, CheckActivity.class);                     intent.putExtra("password",password);                     startActivity(intent);                 }                 break;         }     } } ~~~ **设置手势密码代码:** ~~~ public class SetGestureLockActivity extends Activity {     // 手势密码点的状态     public static final int POINT_STATE_NORMAL = 0; // 正常状态     public static final int POINT_STATE_SELECTED = 1; // 按下状态     public static final int POINT_STATE_WRONG = 2; // 错误状态     @ViewInject(R.id.lock_indicator)     LockIndicator mLockIndicator;     @ViewInject(R.id.text_tip)     TextView mTextTip;     @ViewInject(R.id.gesture_container)     FrameLayout mGestureContainer;     private GestureContentView mGestureContentView;     private boolean mIsFirstInput = true;     private String mFirstPassword = null;     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_set_gesture_lock);         ViewUtils.inject(this);         initParam();     }     private void initParam() {         mGestureContentView = new GestureContentView(this, false, "", new GestureDrawline.GestureCallBack() {             @Override             public void onGestureCodeInput(String inputCode) {                 if (!isInputPassValidate(inputCode)) {                     mTextTip.setText(Html.fromHtml("最少链接4个点, 请重新输入"));                     mGestureContentView.clearDrawlineState(0L);                     return;                 }                 if (mIsFirstInput) {                     mFirstPassword = inputCode;                     updateCodeList(inputCode);                     mGestureContentView.clearDrawlineState(0L);                 } else {                     if (inputCode.equals(mFirstPassword)) {                         Toast.makeText(SetGestureLockActivity.this, "设置成功", Toast.LENGTH_SHORT).show();                         mGestureContentView.clearDrawlineState(0L);                         MainActivity.password =mFirstPassword;                         SetGestureLockActivity.this.finish();                     } else {                         mTextTip.setText(Html.fromHtml("与上一次绘制不一致,请重新绘制"));                         // 左右移动动画                         Animation shakeAnimation = AnimationUtils.loadAnimation(SetGestureLockActivity.this, R.anim.shake);                         mTextTip.startAnimation(shakeAnimation);                         // 保持绘制的线,1.5秒后清除                         mGestureContentView.clearDrawlineState(1300L);                     }                 }                 mIsFirstInput = false;             }             @Override             public void checkedSuccess() {             }             @Override             public void checkedFail() {             }         });         // 设置手势解锁显示到哪个布局里面         mGestureContentView.setParentView(mGestureContainer);         updateCodeList("");     }     private void updateCodeList(String inputCode) {         // 更新选择的图案         mLockIndicator.setPath(inputCode);     }     private boolean isInputPassValidate(String inputPassword) {         if (TextUtils.isEmpty(inputPassword) || inputPassword.length() < 4) {             return false;         }         return true;     } } ~~~ **检验手势密码代码:** ~~~ public class CheckActivity extends AppCompatActivity {     @ViewInject(R.id.text_tip)     TextView mTextTip;     @ViewInject(R.id.gesture_container)     FrameLayout mGestureContainer;     private GestureContentView mGestureContentView;     //设置的手势密码     private String password;     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_check);         ViewUtils.inject(this);         Intent intent = getIntent();         password = intent.getStringExtra("password");         Log.e("Password", password);         initViews();     }     private void initViews() {         // 初始化一个显示各个点的viewGroup         mGestureContentView = new GestureContentView(this, true, password,                 new GestureDrawline.GestureCallBack() {                     @Override                     public void onGestureCodeInput(String inputCode) {                     }                     @Override                     public void checkedSuccess() {                         mGestureContentView.clearDrawlineState(0L);                         Toast.makeText(CheckActivity.this, "密码正确", 1000).show();                         CheckActivity.this.finish();                     }                     @Override                     public void checkedFail() {                         mGestureContentView.clearDrawlineState(1300L);                         mTextTip.setVisibility(View.VISIBLE);                         mTextTip.setText(Html                                 .fromHtml("密码错误"));                         // 左右移动动画                         Animation shakeAnimation = AnimationUtils.loadAnimation(CheckActivity.this, R.anim.shake);                         mTextTip.startAnimation(shakeAnimation);                     }                 });         // 设置手势解锁显示到哪个布局里面         mGestureContentView.setParentView(mGestureContainer);     } } ~~~ package com.hnpolice.luoxiaoke.gesturelock; import android.widget.ImageView; **手势点的代码:** ~~~ /**  * Created by Luoxiaoke on 2015/12/2 14:32.  */ public class GesturePoint {     /**      * 左边x的值      */     private int leftX;     /**      * 右边x的值      */     private int rightX;     /**      * 上边y的值      */     private int topY;     /**      * 下边y的值      */     private int bottomY;     /**      * 这个点对应的ImageView控件      */     private ImageView image;     /**      * 中心x值      */     private int centerX;     /**      * 中心y值      */     private int centerY;     /**      * 状态值      */     private int pointState;     /**      * 代表这个Point对象代表的数字,从1开始(直接感觉从1开始)      */     private int num;     public GesturePoint(int leftX, int rightX, int topY, int bottomY,                         ImageView image, int num) {         super();         this.leftX = leftX;         this.rightX = rightX;         this.topY = topY;         this.bottomY = bottomY;         this.image = image;         this.centerX = (leftX + rightX) / 2;         this.centerY = (topY + bottomY) / 2;         this.num = num;     }     public int getLeftX() {         return leftX;     }     public void setLeftX(int leftX) {         this.leftX = leftX;     }     public int getRightX() {         return rightX;     }     public void setRightX(int rightX) {         this.rightX = rightX;     }     public int getTopY() {         return topY;     }     public void setTopY(int topY) {         this.topY = topY;     }     public int getBottomY() {         return bottomY;     }     public void setBottomY(int bottomY) {         this.bottomY = bottomY;     }     public ImageView getImage() {         return image;     }     public void setImage(ImageView image) {         this.image = image;     }     public int getCenterX() {         return centerX;     }     public void setCenterX(int centerX) {         this.centerX = centerX;     }     public int getCenterY() {         return centerY;     }     public void setCenterY(int centerY) {         this.centerY = centerY;     }     public int getPointState() {         return pointState;     }     public void setPointState(int state) {         pointState = state;         switch (state) {             case SetGestureLockActivity.POINT_STATE_NORMAL:                 this.image.setBackgroundResource(R.mipmap.gesturepassward_locus_round_original);                 break;             case SetGestureLockActivity.POINT_STATE_SELECTED:                 this.image.setBackgroundResource(R.mipmap.gesturepassward_locus_round_click);                 break;             case SetGestureLockActivity.POINT_STATE_WRONG:                 this.image.setBackgroundResource(R.mipmap.gesturepassward_locus_round_wrong);                 break;             default:                 break;         }     }     public int getNum() {         return num;     }     public void setNum(int num) {         this.num = num;     }     @Override     public int hashCode() {         final int prime = 31;         int result = 1;         result = prime * result + bottomY;         result = prime * result + ((image == null) ? 0 : image.hashCode());         result = prime * result + leftX;         result = prime * result + rightX;         result = prime * result + topY;         return result;     }     @Override     public boolean equals(Object obj) {         if (this == obj)             return true;         if (obj == null)             return false;         if (getClass() != obj.getClass())             return false;         GesturePoint other = (GesturePoint) obj;         if (bottomY != other.bottomY)             return false;         if (image == null) {             if (other.image != null)                 return false;         } else if (!image.equals(other.image))             return false;         if (leftX != other.leftX)             return false;         if (rightX != other.rightX)             return false;         if (topY != other.topY)             return false;         return true;     }     @Override     public String toString() {         return "Point [leftX=" + leftX + ", rightX=" + rightX + ", topY="                 + topY + ", bottomY=" + bottomY + "]";     } } ~~~ **手势密码路径绘制代码:** ~~~ public class GestureDrawline extends View {     private int mov_x;// 声明起点坐标     private int mov_y;     private Paint paint;// 声明画笔     private Canvas canvas;// 画布     private Bitmap bitmap;// 位图     private List list;// 装有各个view坐标的集合     private List> lineList;// 记录画过的线     private Map autoCheckPointMap;// 自动选中的情况点     private boolean isDrawEnable = true; // 是否允许绘制     /**      * 屏幕的宽度和高度      */     private int[] screenDispaly;     /**      * 手指当前在哪个Point内      */     private GesturePoint currentPoint;     /**      * 用户绘图的回调      */     private GestureCallBack callBack;     /**      * 用户当前绘制的图形密码      */     private StringBuilder passWordSb;     /**      * 是否为校验      */     private boolean isVerify;     /**      * 用户传入的passWord      */     private String passWord;     public GestureDrawline(Context context, List list, boolean isVerify,                            String passWord, GestureCallBack callBack) {         super(context);         screenDispaly = AppUtil.getScreenDispaly(context);         paint = new Paint(Paint.DITHER_FLAG);// 创建一个画笔         bitmap = Bitmap.createBitmap(screenDispaly[0], screenDispaly[0], Bitmap.Config.ARGB_8888); // 设置位图的宽高         canvas = new Canvas();         canvas.setBitmap(bitmap);         paint.setStyle(Paint.Style.STROKE);// 设置非填充         paint.setStrokeWidth(10);// 笔宽5像素         paint.setColor(Color.rgb(245, 142, 33));// 设置默认连线颜色         paint.setAntiAlias(true);// 不显示锯齿         this.list = list;         this.lineList = new ArrayList>();         initAutoCheckPointMap();         this.callBack = callBack;         // 初始化密码缓存         this.isVerify = isVerify;         this.passWordSb = new StringBuilder();         this.passWord = passWord;     }     private void initAutoCheckPointMap() {         autoCheckPointMap = new HashMap();         autoCheckPointMap.put("1,3", getGesturePointByNum(2));         autoCheckPointMap.put("1,7", getGesturePointByNum(4));         autoCheckPointMap.put("1,9", getGesturePointByNum(5));         autoCheckPointMap.put("2,8", getGesturePointByNum(5));         autoCheckPointMap.put("3,7", getGesturePointByNum(5));         autoCheckPointMap.put("3,9", getGesturePointByNum(6));         autoCheckPointMap.put("4,6", getGesturePointByNum(5));         autoCheckPointMap.put("7,9", getGesturePointByNum(8));     }     private GesturePoint getGesturePointByNum(int num) {         for (GesturePoint point : list) {             if (point.getNum() == num) {                 return point;             }         }         return null;     }     // 画位图     @Override     protected void onDraw(Canvas canvas) {         // super.onDraw(canvas);         canvas.drawBitmap(bitmap, 0, 0, null);     }     // 触摸事件     @Override     public boolean onTouchEvent(MotionEvent event) {         if (isDrawEnable == false) {             // 当期不允许绘制             return true;         }         paint.setColor(Color.rgb(18, 195, 249));// 设置默认连线颜色         switch (event.getAction()) {             case MotionEvent.ACTION_DOWN:                 mov_x = (int) event.getX();                 mov_y = (int) event.getY();                 // 判断当前点击的位置是处于哪个点之内                 currentPoint = getPointAt(mov_x, mov_y);                 if (currentPoint != null) {                     currentPoint.setPointState(SetGestureLockActivity.POINT_STATE_SELECTED);                     passWordSb.append(currentPoint.getNum());                 }                 // canvas.drawPoint(mov_x, mov_y, paint);// 画点                 invalidate();                 break;             case MotionEvent.ACTION_MOVE:                 clearScreenAndDrawList();                 // 得到当前移动位置是处于哪个点内                 GesturePoint pointAt = getPointAt((int) event.getX(), (int) event.getY());                 // 代表当前用户手指处于点与点之前                 if (currentPoint == null && pointAt == null) {                     return true;                 } else {// 代表用户的手指移动到了点上                     if (currentPoint == null) {// 先判断当前的point是不是为null                         // 如果为空,那么把手指移动到的点赋值给currentPoint                         currentPoint = pointAt;                         // 把currentPoint这个点设置选中为true;                         currentPoint.setPointState(SetGestureLockActivity.POINT_STATE_SELECTED);                         passWordSb.append(currentPoint.getNum());                     }                 }                 if (pointAt == null || currentPoint.equals(pointAt) || SetGestureLockActivity.POINT_STATE_SELECTED == pointAt.getPointState()) {                     // 点击移动区域不在圆的区域,或者当前点击的点与当前移动到的点的位置相同,或者当前点击的点处于选中状态                     // 那么以当前的点中心为起点,以手指移动位置为终点画线                     canvas.drawLine(currentPoint.getCenterX(), currentPoint.getCenterY(), event.getX(), event.getY(), paint);// 画线                 } else {                     // 如果当前点击的点与当前移动到的点的位置不同                     // 那么以前前点的中心为起点,以手移动到的点的位置画线                     canvas.drawLine(currentPoint.getCenterX(), currentPoint.getCenterY(), pointAt.getCenterX(), pointAt.getCenterY(), paint);// 画线                     pointAt.setPointState(SetGestureLockActivity.POINT_STATE_SELECTED);                     // 判断是否中间点需要选中                     GesturePoint betweenPoint = getBetweenCheckPoint(currentPoint, pointAt);                     if (betweenPoint != null && SetGestureLockActivity.POINT_STATE_SELECTED != betweenPoint.getPointState()) {                         // 存在中间点并且没有被选中                         Pair pair1 = new Pair(currentPoint, betweenPoint);                         lineList.add(pair1);                         passWordSb.append(betweenPoint.getNum());                         Pair pair2 = new Pair(betweenPoint, pointAt);                         lineList.add(pair2);                         passWordSb.append(pointAt.getNum());                         // 设置中间点选中                         betweenPoint.setPointState(SetGestureLockActivity.POINT_STATE_SELECTED);                         // 赋值当前的point;                         currentPoint = pointAt;                     } else {                         Pair pair = new Pair(currentPoint, pointAt);                         lineList.add(pair);                         passWordSb.append(pointAt.getNum());                         // 赋值当前的point;                         currentPoint = pointAt;                     }                 }                 invalidate();                 break;             case MotionEvent.ACTION_UP:// 当手指抬起的时候                 if (isVerify) {                     // 手势密码校验                     // 清掉屏幕上所有的线,只画上集合里面保存的线                     if (passWord.equals(passWordSb.toString())) {                         // 代表用户绘制的密码手势与传入的密码相同                         callBack.checkedSuccess();                     } else {                         // 用户绘制的密码与传入的密码不同。                         callBack.checkedFail();                     }                 } else {                     callBack.onGestureCodeInput(passWordSb.toString());                 }                 break;             default:                 break;         }         return true;     }     /**      * 指定时间去清除绘制的状态      * @param delayTime 延迟执行时间      */     public void clearDrawlineState(long delayTime) {         if (delayTime > 0) {             // 绘制红色提示路线             isDrawEnable = false;             drawErrorPathTip();         }         new Handler().postDelayed(new clearStateRunnable(), delayTime);     }     /**      * 清除绘制状态的线程      */     final class clearStateRunnable implements Runnable {         public void run() {             // 重置passWordSb             passWordSb = new StringBuilder();             // 清空保存点的集合             lineList.clear();             // 重新绘制界面             clearScreenAndDrawList();             for (GesturePoint p : list) {                 p.setPointState(SetGestureLockActivity.POINT_STATE_NORMAL);             }             invalidate();             isDrawEnable = true;         }     }     /**      * 通过点的位置去集合里面查找这个点是包含在哪个Point里面的      *      * @param x      * @param y      * @return 如果没有找到,则返回null,代表用户当前移动的地方属于点与点之间      */     private GesturePoint getPointAt(int x, int y) {         for (GesturePoint point : list) {             // 先判断x             int leftX = point.getLeftX();             int rightX = point.getRightX();             if (!(x >= leftX && x < rightX)) {                 // 如果为假,则跳到下一个对比                 continue;             }             int topY = point.getTopY();             int bottomY = point.getBottomY();             if (!(y >= topY && y < bottomY)) {                 // 如果为假,则跳到下一个对比                 continue;             }             // 如果执行到这,那么说明当前点击的点的位置在遍历到点的位置这个地方             return point;         }         return null;     }     private GesturePoint getBetweenCheckPoint(GesturePoint pointStart, GesturePoint pointEnd) {         int startNum = pointStart.getNum();         int endNum = pointEnd.getNum();         String key = null;         if (startNum < endNum) {             key = startNum + "," + endNum;         } else {             key = endNum + "," + startNum;         }         return autoCheckPointMap.get(key);     }     /**      * 清掉屏幕上所有的线,然后画出集合里面的线      */     private void clearScreenAndDrawList() {         canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);         for (Pair pair : lineList) {             canvas.drawLine(pair.first.getCenterX(), pair.first.getCenterY(),                     pair.second.getCenterX(), pair.second.getCenterY(), paint);// 画线         }     }     /**      * 校验错误/两次绘制不一致提示      */     private void drawErrorPathTip() {         canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);         paint.setColor(Color.rgb(255,0,0));// 设置默认线路颜色         for (Pair pair : lineList) {             pair.first.setPointState(SetGestureLockActivity.POINT_STATE_WRONG);             pair.second.setPointState(SetGestureLockActivity.POINT_STATE_WRONG);             canvas.drawLine(pair.first.getCenterX(), pair.first.getCenterY(),                     pair.second.getCenterX(), pair.second.getCenterY(), paint);// 画线         }         invalidate();     }     public interface GestureCallBack {         /**          * 用户设置/输入了手势密码          */         public abstract void onGestureCodeInput(String inputCode);         /**          * 代表用户绘制的密码与传入的密码相同          */         public abstract void checkedSuccess();         /**          * 代表用户绘制的密码与传入的密码不相同          */         public abstract void checkedFail();     } } ~~~ **手势密码图案提示代码:** ~~~ public class LockIndicator extends View {     private int numRow = 3;    // 行     private int numColum = 3; // 列     private int patternWidth = 40;     private int patternHeight = 40;     private int f = 5;     private int g = 5;     private int strokeWidth = 3;     private Paint paint = null;     private Drawable patternNoraml = null;     private Drawable patternPressed = null;     private String lockPassStr; // 手势密码     public LockIndicator(Context paramContext) {         super(paramContext);     }     public LockIndicator(Context paramContext, AttributeSet paramAttributeSet) {         super(paramContext, paramAttributeSet, 0);         paint = new Paint();         paint.setAntiAlias(true);         paint.setStrokeWidth(strokeWidth);         paint.setStyle(Paint.Style.STROKE);         patternNoraml = getResources().getDrawable(R.mipmap.gesturepasswar_original);         patternPressed = getResources().getDrawable(R.mipmap.gesturepassward_click);         if (patternPressed != null) {             patternWidth = patternPressed.getIntrinsicWidth();             patternHeight = patternPressed.getIntrinsicHeight();             this.f = (patternWidth / 4);             this.g = (patternHeight / 4);             patternPressed.setBounds(0, 0, patternWidth, patternHeight);             patternNoraml.setBounds(0, 0, patternWidth, patternHeight);         }     }     @Override     protected void onDraw(Canvas canvas) {         if ((patternPressed == null) || (patternNoraml == null)) {             return;         }         // 绘制3*3的图标         for (int i = 0; i < numRow; i++) {             for (int j = 0; j < numColum; j++) {                 paint.setColor(-16777216);                 int i1 = j * patternHeight + j * this.g;                 int i2 = i * patternWidth + i * this.f;                 canvas.save();                 canvas.translate(i1, i2);                 String curNum = String.valueOf(numColum * i + (j + 1));                 if (!TextUtils.isEmpty(lockPassStr)) {                     if (lockPassStr.indexOf(curNum) == -1) {                         // 未选中                         patternNoraml.draw(canvas);                     } else {                         // 被选中                         patternPressed.draw(canvas);                     }                 } else {                     // 重置状态                     patternNoraml.draw(canvas);                 }                 canvas.restore();             }         }     }     @Override     protected void onMeasure(int paramInt1, int paramInt2) {         if (patternPressed != null)             setMeasuredDimension(numColum * patternHeight + this.g                     * (-1 + numColum), numRow * patternWidth + this.f                     * (-1 + numRow));     }     /**      * 请求重新绘制      * @param paramString 手势密码字符序列      */     public void setPath(String paramString) {         lockPassStr = paramString;         invalidate();     } } ~~~ **手势密码容器类代码:** ~~~ public class GestureContentView extends ViewGroup {     private int baseNum = 6;     private int[] screenDispaly;     /**      * 每个点区域的宽度      */     private int blockWidth;     /**      * 声明一个集合用来封装坐标集合      */     private List list;     private Context context;     private boolean isVerify;     private GestureDrawline gestureDrawline;     /**      * 包含9个ImageView的容器,初始化      *      * @param context      * @param isVerify 是否为校验手势密码      * @param passWord 用户传入密码      * @param callBack 手势绘制完毕的回调      */     public GestureContentView(Context context, boolean isVerify, String passWord, GestureDrawline.GestureCallBack callBack) {         super(context);         screenDispaly = AppUtil.getScreenDispaly(context);         blockWidth = screenDispaly[0] / 3;         this.list = new ArrayList();         this.context = context;         this.isVerify = isVerify;         // 添加9个图标         addChild();         // 初始化一个可以画线的view         gestureDrawline = new GestureDrawline(context, list, isVerify, passWord, callBack);     }     private void addChild() {         for (int i = 0; i < 9; i++) {             ImageView image = new ImageView(context);             image.setBackgroundResource(R.mipmap.gesturepassward_locus_round_original);             this.addView(image);             invalidate();             // 第几行             int row = i / 3;             // 第几列             int col = i % 3;             // 定义点的每个属性             int leftX = col * blockWidth + blockWidth / baseNum;             int topY = row * blockWidth + blockWidth / baseNum;             int rightX = col * blockWidth + blockWidth - blockWidth / baseNum;             int bottomY = row * blockWidth + blockWidth - blockWidth / baseNum;             GesturePoint p = new GesturePoint(leftX, rightX, topY, bottomY, image, i + 1);             this.list.add(p);         }     }     public void setParentView(ViewGroup parent) {         // 得到屏幕的宽度         int width = screenDispaly[0];         LayoutParams layoutParams = new LayoutParams(width, width);         this.setLayoutParams(layoutParams);         gestureDrawline.setLayoutParams(layoutParams);         parent.addView(gestureDrawline);         parent.addView(this);     }     @Override     protected void onLayout(boolean changed, int l, int t, int r, int b) {         for (int i = 0; i < getChildCount(); i++) {             //第几行             int row = i / 3;             //第几列             int col = i % 3;             View v = getChildAt(i);             v.layout(col * blockWidth + blockWidth / baseNum, row * blockWidth + blockWidth / baseNum,                     col * blockWidth + blockWidth - blockWidth / baseNum, row * blockWidth + blockWidth - blockWidth / baseNum);         }     }     @Override     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {         super.onMeasure(widthMeasureSpec, heightMeasureSpec);         // 遍历设置每个子view的大小         for (int i = 0; i < getChildCount(); i++) {             View v = getChildAt(i);             v.measure(widthMeasureSpec, heightMeasureSpec);         }     }     /**      * 保留路径delayTime时间长      *      * @param delayTime      */     public void clearDrawlineState(long delayTime) {         gestureDrawline.clearDrawlineState(delayTime);     } } ~~~ 源代码下载地址:[http://download.csdn.net/detail/lxk_1993/9368281](http://download.csdn.net/detail/lxk_1993/9368281)
';