标签 Android 下的文章

项目中用到手势解锁,然而没有在 GitHub 上找到想要的样式= =,只好自己来定义了,下面来看代码~~

基本上很多应用的手势解锁全都是九宫格的,内部内就是九个小圈圈而已。那么我们就先来自定义这个小圈圈吧~

一、状态

圈圈的颜色选择状态有大致有三种状态,所以我定义了一个枚举来区分

package com.juzisang.com.library;

/**
 * Created by 橘子桑 on 2016/3/27.
 */
public enum LockState {
    SELECT_STATE,//选中
    ERRER_STATE, //错误
    DEFAULT_COLOR //默认
}

二、圆圈 View

圈圈分为边框,内部填充色,还有内部圆。所以我定义了三个画笔来区分。

package com.juzisang.com.library;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by 橘子桑 on 2016/3/27.
 */
public class MarkerView extends View {
    //是否显示内部的圈圈
    private boolean mInsideNodeShow;
    //宽度
    protected int mContentWidth;
    //宽度
    protected int mContentRadius;
    //选中状态
    protected LockState mCurrentState = LockState.DEFAULT_COLOR;
    //画边框画圆的的画笔
    private Paint mNodeFramePaint;
    private Paint mNodeCirclePaint;
    private Paint mNodeFullPaint;
    //默认的颜色
    private int mDefaultColor = Color.parseColor("#757575");
    private int mDefailtFullColor = Color.parseColor("#64757575");
    private int mNodeDefaultColor = Color.parseColor("#757575");
    //选中的颜色
    private int mSelectColor = Color.parseColor("#7ECEF4");
    private int mFrameSelectFullColor = Color.parseColor("#647ECEF4");
    private int mNodeSelectColor = Color.parseColor("#7ECEF4");
    //错误时候的颜色
    private int mErrerColor = Color.parseColor("#EC6941");
    private int mErrerFullColor = Color.parseColor("#64EC6941");
    private int mErrerNodeColor = Color.parseColor("#EC6941");
    //边框的宽度
    private int mFrameLineWidth;
    private int mNodeRadius;
    //每个圈圈的内边距
    private int mNodePadding;
    //触摸有效的范围
    private float mTouchRatio;
    //当前标记的位置
    private int mNum;

    public MarkerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context, attrs, 0);
    }

    public MarkerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs, defStyleAttr);
    }
    //以后外部布局传来的参数
    public MarkerView(Context context, int mDefaultColor, int mDefailtFullColor, int mNodeDefaultColor,
                      int mSelectColor, int mFrameSelectFullColor, int mNodeSelectColor,
                      int mErrerColor, int mErrerFullColor, int mErrerNodeColor,
                      int mFrameLineWidth, int mNodeRadius, int mNodePadding, boolean insideNodeShow) {
        super(context);
        this.mInsideNodeShow = insideNodeShow;
        this.mDefaultColor = mDefaultColor;
        this.mDefailtFullColor = mDefailtFullColor;
        this.mNodeDefaultColor = mNodeDefaultColor;
        this.mSelectColor = mSelectColor;
        this.mFrameSelectFullColor = mFrameSelectFullColor;
        this.mNodeSelectColor = mNodeSelectColor;
        this.mErrerColor = mErrerColor;
        this.mErrerFullColor = mErrerFullColor;
        this.mErrerNodeColor = mErrerNodeColor;
        this.mFrameLineWidth = mFrameLineWidth;
        this.mNodeRadius = mNodeRadius;
        this.mNodePadding = mNodePadding;
        //内边距
        setPadding(mNodePadding, mNodePadding, mNodePadding, mNodePadding);
        //外部圆
        mNodeFramePaint = new Paint();
        mNodeFramePaint.setColor(mDefaultColor);
        mNodeFramePaint.setAntiAlias(true);
        mNodeFramePaint.setStrokeWidth(mFrameLineWidth);
        mNodeFramePaint.setStyle(Paint.Style.STROKE);//只画出边框

        //内部填充色
        mNodeFullPaint = new Paint();
        mNodeFullPaint.setColor(mDefailtFullColor);
        mNodeFullPaint.setStyle(Paint.Style.FILL);
        mNodeFullPaint.setAntiAlias(true);

        //内部圆
        mNodeCirclePaint = new Paint();
        mNodeCirclePaint.setColor(mNodeDefaultColor);
        mNodeCirclePaint.setStyle(Paint.Style.FILL);//填充
        mNodeCirclePaint.setAntiAlias(true);
    }

    //取当前透明度的百分比
    public int getFullAlpha(int color, float ratio) {
        return Color.argb((int) (Color.alpha(color) * ratio), Color.red(color), Color.green(color), Color.blue(color));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mContentWidth = getWidth();
        mContentRadius = mContentWidth / 2 - Math.abs(getPaddingLeft()) - mFrameLineWidth / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);


        switch (mCurrentState) {
            case DEFAULT_COLOR: //默认
                mNodeFramePaint.setColor(mDefaultColor);
                mNodeFullPaint.setColor(mDefailtFullColor);
                mNodeCirclePaint.setColor(mNodeDefaultColor);
                //外圆
                canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius, mNodeFramePaint);
                //填充色
                canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius - mFrameLineWidth / 2, mNodeFullPaint);
                //中心圆
                if (mInsideNodeShow)
                    canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mNodeRadius, mNodeCirclePaint);
                break;
            case ERRER_STATE://错误
                mNodeFramePaint.setColor(mErrerColor);
                mNodeFullPaint.setColor(mErrerFullColor);
                mNodeCirclePaint.setColor(mErrerNodeColor);
                //外圆
                canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius, mNodeFramePaint);
                //填充色
                canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius - mFrameLineWidth / 2, mNodeFullPaint);
                //中心圆
                canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mNodeRadius, mNodeCirclePaint);
                break;
            case SELECT_STATE://选中
                mNodeFramePaint.setColor(mSelectColor);
                mNodeFullPaint.setColor(mFrameSelectFullColor);
                mNodeCirclePaint.setColor(mNodeSelectColor);
                //外圆
                canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius, mNodeFramePaint);
                //填充色
                canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius - mFrameLineWidth / 2, mNodeFullPaint);
                //中心圆
                canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mNodeRadius, mNodeCirclePaint);
                break;
        }

    }
    //设置状态,并且重绘
    public void setState(LockState CurrentState) {
        mCurrentState = CurrentState;
        invalidate();
    }
    //是否选中
    public boolean isHighLighted() {
        if (mCurrentState == LockState.SELECT_STATE || mCurrentState == LockState.ERRER_STATE) {
            return true;
        }
        return false;
    }
    //中心点X
    public int getCenterX() {
        return (getLeft() + getRight()) / 2;
    }
    //中心点Y
    public int getCenterY() {
        return (getTop() + getBottom()) / 2;
    }
    //设置圈圈在手势锁当中的位置
    protected void setNum(int num) {
        mNum = num;
    }

    protected int getNum() {
        return mNum;
    }
}

以上就是一个简单的圆了

三、自定义九宫格 View 属性

那么,自定义 View 当然会有自定义属性,所以有这么多 T0T,不要问我为什么这么多属性,任性= =(其实我还想写更多),自定义属性的方法

 <!-- 线的颜色 -->
    <attr name="lineColor" format="color" />
    <!-- 线的宽度 -->
    <attr name="lineWidth" format="dimension" />
    <!--默认颜色 -->
    <attr name="defaultColor" format="color" />
    <!--默认时的填充色-->
    <attr name="defaultFullColor" format="color" />
    <!--默认内部圆颜色-->
    <attr name="defaultNodeColor" format="color" />
    <!-- ======================================================= -->
    <!-- 边框选中时边框的颜色 -->
    <attr name="selectColor" format="color" />
    <!-- 边框选中时内部的填充色 -->
    <attr name="selectFrameFullColor" format="color" />
    <!--内部圆圈选中时的颜色-->
    <attr name="selectNodeColor" format="color" />
    <!-- ======================================================= -->
    <!-- 错误的颜色 -->
    <attr name="errorColor" format="color" />
    <!--错误时内部的填充色-->
    <attr name="errorFullColor" format="color" />
    <!-- 错误时的颜色 -->
    <attr name="errorNodeColor" format="color" />
    <!-- ======================================================= -->
    <!--边框的的宽度-->
    <attr name="frameLineWidth" format="dimension" />
    <!-- 内部圆圈的宽度 -->
    <attr name="nodeRadius" format="dimension" />
    <!--内边距-->
    <attr name="nodePadding" format="dimension" />
    <!--触摸有效的比例-->
    <attr name="touchRatio" format="float" />
    <!-- 是否显示内部的圆圈 -->
    <attr name="insideNodeShow" format="boolean"/>

四、框框 View

LockView 的代码

package com.juzisang.com.library;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;

import java.util.ArrayList;

/**
 * Created by 橘子桑 on 2016/3/27.
 */
public class LockView extends ViewGroup {
    //画连接线的画笔
    private Paint mLinePaint;
    //可以触摸的区域百分比
    private float mTouchRatio;
    //线的颜色
    protected int mLineColor;
    //先的宽度
    protected float mLineWidth;
    //已经选中了的View
    ArrayList<MarkerView> mNodeViews = new ArrayList<>();
    //存储密码
    protected StringBuilder pawBuilder = new StringBuilder();
    //当前手指触摸的x坐标
    protected float x;
    //当前手指触摸的y坐标
    protected float y;
    //回调
    private onLockCallback mOnLockCallback;

    protected int mDefaultColor;

    protected int mSelectColor;

    protected int mErrerColor;

    //禁用手势锁
    private boolean mLockScreen;

    private boolean isTouch;

    //是否把连接线绘制在子View的上面
    private boolean mLineTop = false;
    //手指离开立即重绘
    private boolean mFingerLeaveRedraw = true;

    public LockView(Context context) {
        this(context, null);
    }

    public LockView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LockView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs, defStyleAttr);
    }

    protected void initView(Context context, AttributeSet attrs, int defStyleAttr) {

        TypedArray r = context.obtainStyledAttributes(attrs, R.styleable.MarkerView);

        boolean insideNodeShow = r.getBoolean(R.styleable.LockView_insideNodeShow, false);
        //默认的颜色
        mDefaultColor = r.getColor(R.styleable.LockView_defaultColor, context.getResources().getColor(android.R.color.holo_blue_dark));
        int mDefailtFullColor = r.getColor(R.styleable.LockView_defaultFullColor, getFullAlpha(mDefaultColor, 0.3F));
        int mNodeDefaultColor = (int) r.getColor(R.styleable.LockView_defaultNodeColor, mDefaultColor);
        //选中的颜色
        mSelectColor = (int) r.getColor(R.styleable.LockView_selectColor, context.getResources().getColor(android.R.color.holo_blue_light));
        int mFrameSelectFullColor = r.getColor(R.styleable.LockView_selectFrameFullColor, getFullAlpha(mSelectColor, 0.3F));
        int mNodeSelectColor = r.getColor(R.styleable.LockView_selectNodeColor, mSelectColor);
        //错误时候的颜色
        mErrerColor = r.getColor(R.styleable.LockView_errorColor, context.getResources().getColor(android.R.color.holo_red_light));
        int mErrerFullColor = r.getColor(R.styleable.LockView_errorFullColor, getFullAlpha(mErrerColor, 0.3F));
        int mErrerNodeColor = r.getColor(R.styleable.LockView_errorNodeColor, mErrerColor);
        //圆框变的宽度
        int mFrameLineWidth = (int) r.getDimension(R.styleable.LockView_frameLineWidth, DensityUtils.dip2px(context, 5));
        //内圆的直径
        int mNodeRadius = (int) r.getDimension(R.styleable.LockView_nodeRadius, DensityUtils.dip2px(context, 5));
        //内边距
        int mNodePadding = (int) r.getDimension(R.styleable.LockView_nodePadding, DensityUtils.dip2px(context, 10));
        //触摸有效区域
        mTouchRatio = r.getFloat(R.styleable.LockView_touchRatio, mTouchRatio);
        mLineColor = r.getColor(R.styleable.LockView_lineColor, mDefaultColor);
        mLineWidth = r.getDimension(R.styleable.LockView_lineWidth, DensityUtils.dip2px(context, 5));
        r.recycle();
        //设置线的颜色
        mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLinePaint.setColor(mLineColor);
        mLinePaint.setStyle(Paint.Style.STROKE);
        mLinePaint.setStrokeWidth(mLineWidth);
        mLinePaint.setStrokeCap(Paint.Cap.ROUND);
        mLinePaint.setStrokeJoin(Paint.Join.ROUND);

        for (int i = 0; i < 9; i++) {
            MarkerView view = new MarkerView(context, mDefaultColor, mDefailtFullColor, mNodeDefaultColor, mSelectColor, mFrameSelectFullColor, mNodeSelectColor,
                    mErrerColor, mErrerFullColor, mErrerNodeColor, mFrameLineWidth, mNodeRadius, mNodePadding, insideNodeShow);
            view.setNum(i + 1);
            ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            view.setLayoutParams(params);
            addView(view);
        }

        // 清除FLAG,否则 onDraw() 不会调用,原因是 ViewGroup 默认透明背景不需要调用 onDraw()
        setWillNotDraw(false);

    }

    public int getFullAlpha(int color, float ratio) {
        return Color.argb((int) (Color.alpha(color) * ratio), Color.red(color), Color.green(color), Color.blue(color));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int size = Math.min(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); // 测量宽度
        setMeasuredDimension(size, size);
        for (int i = 0; i < getChildCount(); i++) {
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            float areaWidth = (r - l - getPaddingLeft() * 2) / 3;
            for (int n = 0; n < 9; n++) {
                MarkerView node = (MarkerView) getChildAt(n);
                // 获取3*3宫格内坐标
                int row = n / 3;
                int col = n % 3;
                //加上内间距
                int left = (int) (getPaddingLeft() + col * areaWidth);
                int top = (int) (getPaddingTop() + row * areaWidth);
                int right = (int) (left + areaWidth);
                int bottom = (int) (top + areaWidth);
                node.layout(left, top, right, bottom);
            }
        }
    }

    /**
     * 设置连接线是否绘制在子View的上面
     * true 绘制在子View的上面
     * false 绘制在子View的下面
     *
     * @param isLineTop 设置连接线是否绘制在子View的上面
     */
    public void setLineTop(boolean isLineTop) {
        mLineTop = isLineTop;
        invalidate();
    }

    /**
     * 设置连接线是否绘制在子View的上面
     * true 绘制在子View的上面
     * false 绘制在子View的下面
     */
    public boolean getLineTop() {
        return mLineTop;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (getLockScreen()) {
            invalidate();
            return false;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //恢复默认
                resetDefault();
                x = event.getX();
                y = event.getY();
                isTouch = true;
                break;
            case MotionEvent.ACTION_MOVE:
                x = event.getX(); // 这里要实时记录手指的坐标
                y = event.getY();
                MarkerView nodeView = getNodeAt(x, y);
                //没有选中
                if (nodeView != null && !nodeView.isHighLighted()) {
                    nodeView.setState(LockState.SELECT_STATE);
                    mNodeViews.add(nodeView);
                    //进度
                    if (mOnLockCallback != null) {
                        pawBuilder.setLength(0);
                        for (MarkerView markerView : mNodeViews) {
                            pawBuilder.append(markerView.getNum());
                        }
                        mOnLockCallback.onProgress(pawBuilder.toString(), nodeView.getNum());
                    }
                }
                if (mNodeViews.size() > 0) {
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.i("手指抬起了");
                isTouch = false;
                pawBuilder.setLength(0);
                if (mNodeViews.size() <= 0) return true;
                pawBuilder.delete(0, pawBuilder.length());
                if (mOnLockCallback != null) {
                    for (MarkerView markerView : mNodeViews) {
                        pawBuilder.append(markerView.getNum());
                    }
                    mOnLockCallback.onFinish(pawBuilder.toString());
                }
                if (mFingerLeaveRedraw) {
                    resetDefault();
                } else {
                    invalidate();
                }
                break;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //线画在子view的下面
        if (!mLineTop) onDrawLock(canvas);
    }

    //画子View的地方
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        //放在这里的原因是,线会被子View挡到
        if (mLineTop) onDrawLock(canvas);
    }

    /**
     * 画图的方法
     */
    private void onDrawLock(Canvas canvas) {
        //屏幕锁住了,只画起点到终点的
        if (getLockScreen()) {
            onDrawNodeViewLock(canvas);
            return;
        }
        if (isTouch || mFingerLeaveRedraw) {
            //从第一个和最后一个的连接线
            onDrawNodeViewLock(canvas);
            //最后一个点,到手指之间的线
            if (mNodeViews.size() > 0) {
                MarkerView lastNode = mNodeViews.get(mNodeViews.size() - 1);
                canvas.drawLine(lastNode.getCenterX(), lastNode.getCenterY(), x, y, mLinePaint);
            }
        } else {
            //如果手指离开屏幕,并且设置了手指离开立即重绘
            onDrawNodeViewLock(canvas);
        }


    }

    private void onDrawNodeViewLock(Canvas canvas) {
        //从第一个和最后一个的连接线
        for (int i = 1; i < mNodeViews.size(); i++) {
            MarkerView frontNode = mNodeViews.get(i - 1);
            MarkerView backNode = mNodeViews.get(i);
            canvas.drawLine(frontNode.getCenterX(), frontNode.getCenterY(), backNode.getCenterX(), backNode.getCenterY(), mLinePaint);
        }
    }

    /**
     * 获取给定坐标点的Node,返回null表示当前手指在两个Node之间
     */
    private MarkerView getNodeAt(float x, float y) {
        for (int n = 0; n < getChildCount(); n++) {
            MarkerView node = (MarkerView) getChildAt(n);
            //计算触摸区域以外的距离
            float ratioPadding = (node.getWidth() - (node.getWidth() * mTouchRatio)) / 2;
            if (!(x >= node.getLeft() + ratioPadding && x < node.getRight() - ratioPadding)) {
                continue;
            }
            if (!(y >= node.getTop() + ratioPadding && y < node.getBottom() - ratioPadding)) {
                continue;
            }
            return node;
        }
        return null;
    }

    /**
     * 设置连接线的颜色
     *
     * @param color 颜色值
     */
    public void setLineColor(int color) {
        mLinePaint.setColor(color);
    }

    /**
     * 手指离开立即重绘
     */
    public void setfingerLeaveRedraw(boolean mFingerLeaveRedraw) {
        this.mFingerLeaveRedraw = mFingerLeaveRedraw;
    }

    public boolean getfingerLeaveRedraw() {
        return this.mFingerLeaveRedraw;
    }

    /**
     * 重置状态 为默认状态
     */
    public void resetDefault() {
        setState(LockState.DEFAULT_COLOR);
        mNodeViews.clear();
    }

    /**
     * 重置状态错误状态
     */
    public void resetErrer() {
        setState(LockState.ERRER_STATE);
    }

    /**
     * 重置为选中状态
     */
    public void resetSelect() {
        setState(LockState.SELECT_STATE);
    }

    /**
     * 锁屏,不允许触摸
     */
    public void LockScreen(boolean isScreen) {
        mLockScreen = isScreen;
    }

    public boolean getLockScreen() {
        return mLockScreen;
    }

    public void setState(LockState state) {
        switch (state) {
            case DEFAULT_COLOR:
            case SELECT_STATE:
                setLineColor(mSelectColor);
                break;
            case ERRER_STATE:
                setLineColor(mErrerColor);
                break;
        }
        int size = mNodeViews.size();
        for (int i = 0; i < size; i++) {
            mNodeViews.get(i).setState(state);
        }
        invalidate();
    }

    public void setLockCallback(onLockCallback lockCallback) {
        mOnLockCallback = lockCallback;
    }
    //回调
    public interface onLockCallback {

        void onProgress(String paw, int current);

        void onFinish(String paw);
    }
}

以上注释都写的很清楚了,下面讲一下遇到的一些问题。

五、遇到的问题

画出来的线被上面的圈圈覆盖了

20160404155949303.png
通过百度,知道 ViewGroup 的 onDraw 是画布局中的内容的,画子 View 的的方法在这个方法的后面执行,所以 ViewGroup 的内容会被子 View 覆盖,那么怎么才能把连接线画在子 View 的上面呢,很简单
只要在画子 View 的方法中执行就好了

//画子View的地方
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        //放在这里的原因是,线会被子View挡到
        if (mLineTop) onDrawLock(canvas);
    }

下面是 View 的 draw()方法

 @CallSuper
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            //这里就是画子View的方法了
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // we're done...
            return;
        }

怎么设置触摸的区域?

    /**
     * 获取给定坐标点的Node,返回null表示当前手指在两个Node之间
     */
    private MarkerView getNodeAt(float x, float y) {
        for (int n = 0; n < getChildCount(); n++) {
            MarkerView node = (MarkerView) getChildAt(n);
            //计算触摸区域以外的距离
            float ratioPadding = (node.getWidth() - (node.getWidth() * mTouchRatio)) / 2;
            if (!(x >= node.getLeft() + ratioPadding && x < node.getRight() - ratioPadding)) {
                continue;
            }
            if (!(y >= node.getTop() + ratioPadding && y < node.getBottom() - ratioPadding)) {
                continue;
            }
            return node;
        }
        return null;
    }

看上面代码,
根据圆圈的宽度减去可触摸区域的长度除 2,得到可触摸区域距离边框的距的距离。
光看代码看着有点圆,画个图看一下吧
20160404162710907.png

画个图是不是清晰很多,只要用 getLeft+边距,和 getRight-边距,就能得到可触摸区域在 x 轴上的范围了,Y 轴同理,不懂的同学自己用笔画一下吧~

差不多就上面两个问题了
20160404164051803.gif

下载地址

最近学习 Html,需要调用传递数据到 IOS 和 Android,写博客记录下~

一、判断设备是 Android 还是 IOS

var browser = {
  versions: (function() {
    var a = navigator.userAgent,
      b = navigator.appVersion;
    return {
      trident: a.indexOf("Trident") > -1,
      presto: a.indexOf("Presto") > -1,
      webKit: a.indexOf("AppleWebKit") > -1,
      gecko: a.indexOf("Gecko") > -1 && a.indexOf("KHTML") == -1,
      mobile: !!a.match(/AppleWebKit.*Mobile.*/),
      ios: !!a.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),
      android: a.indexOf("Android") > -1 || a.indexOf("Linux") > -1,
      iPhone: a.indexOf("iPhone") > -1,
      iPad: a.indexOf("iPad") > -1,
      webApp: a.indexOf("Safari") == -1
    };
  })(),
  language: (navigator.browserLanguage || navigator.language).toLowerCase()
};

用法:

if (browser.versions.android) {
  //Android
} else if (browser.versions.ios) {
  //ios
}

二、调用 Android 的代码

Android 这边相对 IOS 要简单很多,叫 Android 提供一个类名,方法名直接调用即可

//这里的Android全部都是Android程序猿给你里,叫他提供这两个调用就可以了
//data为传递过去的数据
Android.Android(data);

三、调用 IOS 的代码

function GoIos(data) {
    var iframe = document.createElement("iframe");
    var url = "myapp:";
    url = url + "&data=" + data;
    iframe.src = url;
    iframe.style.display = 'none';
    document.body.appendChild(iframe);
    iframe.parentNode.removeChild(iframe);
    iframe = null;
}

data 为 String 数据,传递过去,IOS 就会触发相应回调了

四、应用

//照上面的写法,代码应该如下
function GoToApp(data){
//只能传递字符串,如果是JSON,转成字符串
data = JSON.stringify(data);
if (browser.versions.android) {
        //Android
        Android.Android(data);
    } else if (browser.versions.ios) {
        //ios
        GoIos(data);
    }
}

参考:
js(javascript)与 ios(Objective-C)相互通信交互

一、前言

最近使用到 RecyclerView,RecyclerView 使用详解戳这里,由于使用过张鸿洋大神的ListView 万能 Adapter,感觉 RecyclerView 的 Adapter 编写还是太麻烦了,而且没有点击事件,ok,参考 ListView 的万能 Adapter 的思路,写一个 RecyclerView 通用的 Adapter,在加上点击效果( ̄︶ ̄)↗ 涨

二、代码编写

RecyclerAdapter 的编写

package com.example.admin.recyclerviewdemo;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

/**
 * Created by 橘子桑 on 2016/1/2.
 */
public abstract class RecyclerAdapter<T> extends RecyclerView.Adapter<RecyclerViewHolder> {

    private Context mContext;
    private List<T> mDatas;
    private int mLayoutId;
    private LayoutInflater mInflater;

    private OnItemClickListener onItemClickListener;

    public RecyclerAdapter(Context mContext, List<T> mDatas, int mLayoutId) {
        this.mContext = mContext;
        this.mDatas = mDatas;
        this.mLayoutId = mLayoutId;
        mInflater = LayoutInflater.from(mContext);
    }
    @Override
    public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //这里是创建ViewHolder的地方,RecyclerAdapter内部已经实现了ViewHolder的重用
        //这里我们直接new就好了
        return new RecyclerViewHolder(mInflater.inflate(mLayoutId, parent, false));
    }

    @Override
    public void onBindViewHolder(RecyclerViewHolder holder, int position) {

    }

    public abstract void convert(RecyclerViewHolder holder, T data, int position);
    @Override
    public int getItemCount() {
        return mDatas.size();
    }
    /**自定义RecyclerView item的点击事件的点击事件*/
    interface OnItemClickListener {
        void OnItemClickListener(View view, int position);
    }
}

上面参考 ListView 的万能适配器,写的一个抽象类,可以看到 onBindViewHolder 方法里面是空的,我们需要在这里来,加入点击事件和效果。

RecyclerHolder

package com.example.admin.recyclerviewdemo;

import android.graphics.Bitmap;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

/**
 * Created by 橘子桑 on 2016/1/2.
 */
public class RecycleHolder extends RecyclerView.ViewHolder {

    /** 用于存储当前item当中的View */
    private SparseArray<View> mViews;

    public RecycleHolder(View itemView) {
        super(itemView);
        mViews = new SparseArray<View>();
    }
    public <T extends View> T findView(int ViewId) {
        View view = mViews.get(ViewId);
        //集合中没有,则从item当中获取,并存入集合当中
        if (view == null) {
            view = itemView.findViewById(ViewId);
            mViews.put(ViewId, view);
        }
        return (T) view;
    }
    public RecycleHolder setText(int viewId, String text) {
        TextView tv = findView(viewId);
        tv.setText(text);
        return this;
    }
    public RecycleHolder setText(int viewId, int text) {
        TextView tv = findView(viewId);
        tv.setText(text);
        return this;
    }
    public RecycleHolder setImageResource(int viewId, int ImageId) {
        ImageView image = findView(viewId);
        image.setImageResource(ImageId);
        return this;
    }
    public RecycleHolder setImageBitmap(int viewId, Bitmap bitmap) {
        ImageView image = findView(viewId);
        image.setImageBitmap(bitmap);
        return this;
    }
    public RecycleHolder setImageNet(int viewId, String url) {
        ImageView image = findView(viewId);
        //使用你所用的网络框架等
        return this;
    }
}

可以看的上面代码非常的简单,就是储存了当前 item 中的 View 而已,因为 RecyclerView 内部已经实现了 ViewHolder 的重用<( ̄︶ ̄)>

为 RecyclerView 添加 item 的点击事件

   @Override
    public void onBindViewHolder(final RecycleHolder holder, int position) {
        if (onItemClickListener != null) {
            //设置背景
            holder.itemView.setBackgroundResource(R.drawable.recycler_bg);
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //注意,这里的position不要用上面参数中的position,会出现位置错乱
                    onItemClickListener.OnItemClickListener(holder.itemView, holder.getLayoutPosition());
                }
            });
        }
        convert(holder, mDatas.get(position), position);
    }
    public abstract void convert(RecycleHolder holder, T data, int position);

上面就完成了,万能适配器的编写,还有点击事件的添加了

drawable xml 代码

为了让 5.0 以上的系统产生水波纹效果,所以我们新建一个 drawable-v21 目录来存放 drawable 文件
<br/>
20160102193704005.png
<br/>

5.0 以下 drawable 文件

recycler_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" >
        <shape android:shape="rectangle">
            <solid android:color="#cfd8dc"></solid>
        </shape>
    </item>
</selector>

<br/>

5.0 以上 drawable-v21

recycler_rectangle.xml
recycler_bg.xml

<!--点击出现的水波纹效果是矩形的 -->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <solid android:color="#FFFFFF" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<!-- ripple 是5.0才出现的新标签-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="#cfd8dc" ><!-- 点击出现的水波纹的颜色 -->
    <item android:drawable="@drawable/recycler_rectangle"/>
</ripple>

三、使用

写个匿名类就 OK 了 ╭(′▽`)╯ 是不是比以前简单好多

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RecyclerView rv_list = findView(R.id.rv_list);
        rv_list.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        rv_list.setAdapter(TextAdapter = new RecyclerAdapter<String>(this, getData(), R.layout.recycler_item) {
            @Override
            public void convert(RecycleHolder holder, String data, int position) {
                holder.setText(R.id.tv, data);
                holder.setImageResource(R.id.image, R.mipmap.ic_launcher);
            }
        });
        TextAdapter.setOnItemClickListener(new RecyclerAdapter.OnItemClickListener() {
            @Override
            public void OnItemClickListener(View view, int position) {
                ToastShow("点击" + position);

            }
        });
    }

四、效果

5.0 的效果
20160102192402887.gif

代码下载地址 http://download.csdn.net/detail/qq_29262849/9387198