仿支付宝向商户付款的个人界面

      上周项目有个需求就是项目增加一个额外的功能就是提供一个向商户付款的功能,当然这个功能感觉也不难实现问题是交互设计没有,自行发挥,于是我仿照了支付宝的,效果如下:


点击条形码和放大的效果


功能主要是自定义气泡popupwindow实现,和使用谷歌官方提供的Zxing实现的带有logo二维码和条形码

一 , pupopwindow 气泡自定义:

public class PopuView extends View {

    private float dimension;
    private int color;
    private int borderColor;
    private int widSize;
    private int heightSiZe;
    private Paint paint;
    private Context mcontext;


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

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


    public PopuView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mcontext = context;
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PopuView);
        dimension = typedArray.getDimension(R.styleable.PopuView_ridus, 0);
        color = typedArray.getColor(R.styleable.PopuView_popu_bg, context.getResources().getColor(R.color.white));
        borderColor = typedArray.getColor(R.styleable.PopuView_popu_border, 0xcccccc);
        typedArray.recycle();

        initPaint();


    }

    private void initPaint() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setColor(borderColor);
    }

    @Override  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widMode = MeasureSpec.getMode(widthMeasureSpec);
        widSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        heightSiZe = MeasureSpec.getSize(heightMeasureSpec);
        if (widMode == MeasureSpec.AT_MOST){
            widSize = 140;
        }
        if (heightMode == MeasureSpec.AT_MOST){
            heightSiZe =52;
        }
        setMeasuredDimension(widSize,heightSiZe);
    }


    @Override  protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(color);
        RectF rectF = new RectF(0,20, widSize,heightSiZe);
        canvas.drawRoundRect(rectF,dimension,dimension,paint);
        Path path = new Path();
        path.moveTo(widSize-60,20);
        path.lineTo(widSize-40,0);
        path.lineTo(widSize-20,20);
        path.close();
        canvas.drawPath(path,paint);

        paint.setColor(mcontext.getResources().getColor(R.color.black));
        paint.setTextSize(DisplayUtil.dip2px(mcontext,14));
        canvas.drawText("刷新付款码", DisplayUtil.dip2px(mcontext,15),DisplayUtil.dip2px(mcontext,30),paint);
    }


}

onMeasure 里主要决定这个pupopwindow画多大,因为根据项目ui提供的大小,所以我们设置在用户没有指定控件的大小的时候,让这个控件自己画宽140px,height为52px,当然你们可以直接适配用dp2px.其中我们暴露出两个方法,让用户自己去实现就是圆角半径和实体颜色以及边框背景颜色,当然这都可以根据用户自己的需求来,比如可以让里面的文本和popupwindow的高度(因为有可能是数组文本和集合文本)都可以自己设置,我这里就怎么简单怎么来啦~,暴露出的自定义属性

<declare-styleable name="PopuView">
    
    <attr name="ridus" format="dimension|integer"></attr>
    <attr name="popu_bg" format="color"></attr>
    <attr name="popu_border" format="color"></attr>
    
</declare-styleable>

然后就是自己绘制边框和文本了,这个方法就这么几行很简单,尖角嘛,就是两条path就搞定了.之后在activity里把他show出来就好了

private void showPopupWindow(View anchorView){
    View contentView = getLayoutInflater().inflate(R.layout.view_popup, null);
    PopuView popuView = (PopuView) contentView.findViewById(R.id.popuview);

    mPopupWindow = new PopupWindow(contentView,
            ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
    // 如果不设置PopupWindow的背景,有些版本就会出现一个问题:无论是点击外部区域还是Back键都无法dismiss弹框
    mPopupWindow.setBackgroundDrawable(new ColorDrawable());
    // 设置好参数之后再show
    int windowPos[] = PopupWindowUtil.calculatePopWindowPos(anchorView, contentView);
    int xOff = 30; // 可以自己调整偏移
    windowPos[0] -= xOff;
    mPopupWindow.showAtLocation(anchorView, Gravity.TOP | Gravity.START, windowPos[0], windowPos[1]);

    popuView.setOnClickListener(new View.OnClickListener() {
        @Override  public void onClick(View v) {
            toast("刷新付款码");
        }
    });
}


二, 条形码的实现主要是Zxing包实现,参数传的就是控件的内容,控件的宽高,考虑到适配就是要用dp转px,不多说直接上代码:

public static Bitmap createBarcodeBitmap(String content,int width, int height){
    com.google.zxing.Writer c9 = new Code128Writer();
    try {
        BitMatrix bm = c9.encode(content,BarcodeFormat.CODE_128,350, 350);
        bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                bitmap.setPixel(i, j, bm.get(i, j) ? Color.BLACK : Color.WHITE);
            }
        }
    } catch (WriterException e) {
        e.printStackTrace();
    }

    return bitmap;
}

返回一个bitmap对象,然后将这个bitmap设置ImageView

barcodeBitmap = ZxUtils.createBarcodeBitmap("123", DisplayUtil.dip2px(this, 285), DisplayUtil.dip2px(this, 70));
if (null!= barcodeBitmap){
    ivBarcode.setImageBitmap(barcodeBitmap);


}

三, 同理也是Zxing实现带有logo的二维码,主要有两种方式,一种是先实现二维码,然后再在二维码中间画一个logo的bitmap,二是直接将二维码和logo的bitmap进行合并创建一个新的bitmap,根据项目需求我们直接使用了bitmap和logo混合后合并一个新的bitmap

/**  * 生成带有logo的二维码  * @param content  * @param logoBitmap  * @param qrWidth 二维码宽度  * @return  */ public static Bitmap createQRBitmapWithLogo(String content,Bitmap logoBitmap,int qrWidth){


    final int LOGO_WIDTH_MAX = qrWidth / 15;
    final int LOGO_WIDTH_MIN = qrWidth/15;

    int logoWidth = logoBitmap.getWidth();
    int logoHeight = logoBitmap.getHeight();
    int logoHaleWidth = logoWidth >= qrWidth ? LOGO_WIDTH_MIN
            : LOGO_WIDTH_MAX;
    int logoHaleHeight = logoHeight >= qrWidth ? LOGO_WIDTH_MIN
            : LOGO_WIDTH_MAX;
    // logo图片按martix设置的信息缩放
    Matrix m = new Matrix();
    /*
     * 给的源码是,由于CSDN上传的资源不能改动,这里注意改一下
     * float sx = (float) 2*logoHaleWidth / logoWidth;
     * float sy = (float) 2*logoHaleHeight / logoHeight;
     */
    float sx = (float) logoHaleWidth / logoWidth;
    float sy = (float) logoHaleHeight / logoHeight;
    m.setScale(1, 1);// 设置缩放信息
    Bitmap newLogoBitmap = Bitmap.createBitmap(logoBitmap, 0, 0, logoWidth,
            logoHeight, m, false);
    int newLogoWidth = newLogoBitmap.getWidth();
    int newLogoHeight = newLogoBitmap.getHeight();
    Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
    hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
    hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);//设置容错级别,H为最高
    hints.put(EncodeHintType.MAX_SIZE, LOGO_WIDTH_MAX);// 设置图片的最大值
    hints.put(EncodeHintType.MIN_SIZE, LOGO_WIDTH_MIN);// 设置图片的最小值
    hints.put(EncodeHintType.MARGIN, 2);//设置白色边距值
    // 生成二维矩阵,编码时指定大小,不要生成了图片以后再进行缩放,这样会模糊导致识别失败
    BitMatrix matrix = null;
    try {
        matrix = new MultiFormatWriter().encode(content,
                BarcodeFormat.QR_CODE, qrWidth, qrWidth, hints);
    } catch (WriterException e) {
        e.printStackTrace();
    }
    int width = matrix.getWidth();
    int height = matrix.getHeight();
    int halfW = width / 2;
    int halfH = height / 2;
    // 二维矩阵转为一维像素数组,也就是一直横着排了
    int[] pixels = new int[width * height];
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
        /*
             * 取值范围,可以画图理解下
             * halfW + newLogoWidth / 2 - (halfW - newLogoWidth / 2) = newLogoWidth
             * halfH + newLogoHeight / 2 - (halfH - newLogoHeight) = newLogoHeight
             */
            if (x > halfW - newLogoWidth / 2&& x < halfW + newLogoWidth / 2
                    && y > halfH - newLogoHeight / 2 && y < halfH + newLogoHeight / 2) {// 该位置用于存放图片信息
                /*
                 *  记录图片每个像素信息
                 *  halfW - newLogoWidth / 2 < x < halfW + newLogoWidth / 2
                 *  --> 0 < x - halfW + newLogoWidth / 2 < newLogoWidth
                 *   halfH - newLogoHeight / 2  < y < halfH + newLogoHeight / 2
                 *   -->0 < y - halfH + newLogoHeight / 2 < newLogoHeight
                 *   刚好取值newLogoBitmapgetPixel(0-newLogoWidth,0-newLogoHeight);
                 */
                pixels[y * width + x] = newLogoBitmap.getPixel(
                        x - halfW + newLogoWidth / 2, y - halfH + newLogoHeight / 2);
            } else {
                pixels[y * width + x] = matrix.get(x, y) ? BLACK: WHITE;// 设置信息
            }
        }
    }
    Bitmap bitmap = Bitmap.createBitmap(width, height,
            Bitmap.Config.ARGB_8888);
    // 通过像素数组生成bitmap,具体参考api
    bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
    return bitmap;


}

代码里展示出来

Bitmap logo = BitmapFactory.decodeResource(getResources(), R.drawable.quick_pass_l);
qrBitmapWithLogo = ZxUtils.createQRBitmapWithLogo("987654321", logo, DisplayUtil.dip2px(this, 190));
if (null!= qrBitmapWithLogo){
    ivQrcode.setImageBitmap(qrBitmapWithLogo);
}

给条形码和二维码加上点击动画,如同支付宝,这里没有录动画的vid,你们可以看支付宝,然后来看我这里的实现方法,就行了

条形码放大,旋转和透明动画

private void enlargeBarcodeAnimation() {
    ivAnimation.setImageBitmap(barcodeBitmap);
    rlTitleView.setBackgroundColor(getResources().getColor(R.color.white));
    rlWhole.setBackgroundColor(getResources().getColor(R.color.white));
    llWhole.setVisibility(View.GONE);
    ivAnimation.setVisibility(View.VISIBLE);
    //放大1.5
    ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(ivAnimation,"scaleX",1f,1.5f);
    ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(ivAnimation,"scaleY",1f,1.5f);
    //旋转动画
    ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(ivAnimation,"rotation",0f,90f);
    //透明动画
    ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(ivAnimation, "alpha", 0f, 1f);

    AnimatorSet set = new AnimatorSet();
    set.play(scaleXAnimator).with(scaleYAnimator).with(rotateAnimator).with(alphaAnimator);

    set.setDuration(500);

    set.start();

}

二维码的放大旋转透明的动画集:

private void enlargeQRcodeAnimation() {
    ivQRcodeAnimation.setImageBitmap(qrBitmapWithLogo);
    rlTitleView.setBackgroundColor(getResources().getColor(R.color.white));
    rlWhole.setBackgroundColor(getResources().getColor(R.color.white));
    llWhole.setVisibility(View.GONE);
    ivAnimation.setVisibility(View.GONE);
    ivQRcodeAnimation.setVisibility(View.VISIBLE);

    //放大1.5
    ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(ivQRcodeAnimation,"scaleX",0.5f,1f);
    ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(ivQRcodeAnimation,"scaleY",0.5f,1f);

    //透明动画
    ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(ivQRcodeAnimation, "alpha", 0f, 1f);

    AnimatorSet set = new AnimatorSet();
    set.play(scaleXAnimator).with(scaleYAnimator).with(alphaAnimator);

    set.setDuration(500);

    set.start();
}
这里主要实现思路就是屏幕中间也是有Imageview,然后点击条形码或者二维码的时候,执行之间的动画就行了