WeChat의 등장으로 QR 코드는 쇼핑몰, KFC, 레스토랑 등 어디에서나 볼 수 있습니다. QR 코드 스캔에는 Google의 오픈 소스 프레임워크인 Zxing이 사용됩니다. http://code.google.com/p/zxing/에 가서 소스 코드와 Jar 패키지를 다운로드할 수 있습니다. 이전에는 내 프로젝트의 QR 코드 스캔 기능이 스캔 기능만 구현했고 UI가 정말 못생겼습니다. 좋음 응용 프로그램 소프트웨어의 UI 인터페이스는 대중이 받아들여야 합니다. 그렇지 않으면 사람들이 귀하의 소프트웨어를 사용하지 않을 것입니다. 따라서 응용 프로그램 소프트웨어 기능은 인터페이스만큼 중요합니다. 예를 들어 WeChat UI는 다음과 같습니다. WeChat에서 QR 코드를 스캔하는 효과도 많이 모방했습니다. WeChat만큼 정교하지는 않지만 효과는 여전히 좋기 때문에 UI 수정 코드와 스캔 코드를 공유하겠습니다. 첫째, 앞으로의 프로젝트에서 동일한 기능을 직접 복사하여 사용할 수 있습니다. 둘째는 QR 코드 기능을 추가하지 않은 사람들에게 참고할 것입니다. 다음으로 이 기능을 단계별로 구현하고 불필요한 파일을 많이 제거했습니다
먼저 프로젝트의 구조를 살펴보겠습니다
프로젝트에 동일한 작업을 수행하려면 이 기능을 추가하려면 com.mining.app.zxing.camera, com.mining.app.zxing.decoding 세 가지 패키지를 직접 복사하세요. , com.mining.app.zxing.view 를 프로젝트에 추가한 다음 관련 리소스를 입력하려면 내 프로젝트에서 직접 인용해야 합니다. 물론 패키지 이름도 변경해야 합니다. MipcaActivityCapture가 포함된 Zxing.jar
com.example.qr_codescan 패키지는 또한 이전 프로젝트의 코드를 직접 소개합니다. 예를 들어, 이 활동은 주로 스캐닝 인터페이스 클래스를 처리합니다. 스캔이 성공하면 소리와 진동 등이 발생합니다. 스캔이 완료된 후 스캔은 결과의 초기 매개변수와 QR의 비트맵을 주로 내부의 handlerDecode(결과 결과, 비트맵 바코드) 방법에 중점을 둡니다. 코드는 handlerDecode(결과 결과, 비트맵 바코드)로 전달됩니다. 해당 처리 코드만 작성하면 됩니다. 다른 위치를 변경할 필요는 없습니다. 스캔하여 찍은 사진은 여기에서 처리하겠습니다.
/** * 处理扫描结果 * @param result * @param barcode */ public void handleDecode(Result result, Bitmap barcode) { inactivityTimer.onActivity(); playBeepSoundAndVibrate(); String resultString = result.getText(); if (resultString.equals("")) { Toast.makeText(MipcaActivityCapture.this, "Scan failed!", Toast.LENGTH_SHORT).show(); }else { Intent resultIntent = new Intent(); Bundle bundle = new Bundle(); bundle.putString("result", resultString); bundle.putParcelable("bitmap", barcode); resultIntent.putExtras(bundle); this.setResult(RESULT_OK, resultIntent); } MipcaActivityCapture.this.finish(); }MipcaActivityCapture 인터페이스의 레이아웃을 직접 변경했습니다. 먼저 렌더링을 살펴보겠습니다. 저는 주로 FrameLayout을 내부에 중첩하여 사용합니다. 레이아웃 코드는 다음과 같습니다
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" > <SurfaceView android:id="@+id/preview_view" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_gravity="center" /> <com.mining.app.zxing.view.ViewfinderView android:id="@+id/viewfinder_view" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <include android:id="@+id/include1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" layout="@layout/activity_title" /> </RelativeLayout> </FrameLayout>그 안에는 다른 인터페이스를 레이아웃에 포함시킨 다음 이 활동 제목을 내 프로젝트의 다른 활동에서도 사용하기 때문에 직접 복사했습니다.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@drawable/mmtitle_bg_alpha" > <Button android:id="@+id/button_back" android:layout_width="75.0dip" android:text="返回" android:background="@drawable/mm_title_back_btn" android:textColor="@android:color/white" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="2dip" /> <TextView android:id="@+id/textview_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/button_back" android:layout_alignBottom="@+id/button_back" android:layout_centerHorizontal="true" android:gravity="center_vertical" android:text="二维码扫描" android:textColor="@android:color/white" android:textSize="18sp" /> </RelativeLayout>in my 이 데모에는 Button, ImageView 및 TextView가 있는 MainActivity가 있습니다. 버튼을 클릭하면 QR 코드 스캔 인터페이스로 들어가고 스캔이 완료되면 메인 인터페이스로 돌아가서 스캔 결과를 표시합니다. TextView에 그림이 표시되면 여기에 그림을 추가할 필요가 없습니다.
<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" android:background="#ffe1e0de" > <Button android:id="@+id/button1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:text="扫描二维码" /> <TextView android:id="@+id/result" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@+id/button1" android:lines="2" android:gravity="center_horizontal" android:textColor="@android:color/black" android:textSize="16sp" /> <ImageView android:id="@+id/qrcode_bitmap" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_alignParentLeft="true" android:layout_below="@+id/result"/> </RelativeLayout>
package com.example.qr_codescan; import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; public class MainActivity extends Activity { private final static int SCANNIN_GREQUEST_CODE = 1; /** * 显示扫描结果 */ private TextView mTextView ; /** * 显示扫描拍的图片 */ private ImageView mImageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.result); mImageView = (ImageView) findViewById(R.id.qrcode_bitmap); //点击按钮跳转到二维码扫描界面,这里用的是startActivityForResult跳转 //扫描完了之后调到该界面 Button mButton = (Button) findViewById(R.id.button1); mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setClass(MainActivity.this, MipcaActivityCapture.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivityForResult(intent, SCANNIN_GREQUEST_CODE); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case SCANNIN_GREQUEST_CODE: if(resultCode == RESULT_OK){ Bundle bundle = data.getExtras(); //显示扫描到的内容 mTextView.setText(bundle.getString("result")); //显示 mImageView.setImageBitmap((Bitmap) data.getParcelableExtra("bitmap")); } break; } } }위 코드에서 가운데 줄은 위챗에서 사용하는 그림을 여기에 그렸습니다. 좀 더 시뮬레이션하고 싶다면 다음 코드를
/* * Copyright (C) 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.mining.app.zxing.view; import java.util.Collection; import java.util.HashSet; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.util.AttributeSet; import android.view.View; import com.example.qr_codescan.R; import com.google.zxing.ResultPoint; import com.mining.app.zxing.camera.CameraManager; /** * This view is overlaid on top of the camera preview. It adds the viewfinder * rectangle and partial transparency outside it, as well as the laser scanner * animation and result points. * */ public final class ViewfinderView extends View { private static final String TAG = "log"; /** * 刷新界面的时间 */ private static final long ANIMATION_DELAY = 10L; private static final int OPAQUE = 0xFF; /** * 四个绿色边角对应的长度 */ private int ScreenRate; /** * 四个绿色边角对应的宽度 */ private static final int CORNER_WIDTH = 10; /** * 扫描框中的中间线的宽度 */ private static final int MIDDLE_LINE_WIDTH = 6; /** * 扫描框中的中间线的与扫描框左右的间隙 */ private static final int MIDDLE_LINE_PADDING = 5; /** * 中间那条线每次刷新移动的距离 */ private static final int SPEEN_DISTANCE = 5; /** * 手机的屏幕密度 */ private static float density; /** * 字体大小 */ private static final int TEXT_SIZE = 16; /** * 字体距离扫描框下面的距离 */ private static final int TEXT_PADDING_TOP = 30; /** * 画笔对象的引用 */ private Paint paint; /** * 中间滑动线的最顶端位置 */ private int slideTop; /** * 中间滑动线的最底端位置 */ private int slideBottom; private Bitmap resultBitmap; private final int maskColor; private final int resultColor; private final int resultPointColor; private Collection<ResultPoint> possibleResultPoints; private Collection<ResultPoint> lastPossibleResultPoints; boolean isFirst; public ViewfinderView(Context context, AttributeSet attrs) { super(context, attrs); density = context.getResources().getDisplayMetrics().density; //将像素转换成dp ScreenRate = (int)(20 * density); paint = new Paint(); Resources resources = getResources(); maskColor = resources.getColor(R.color.viewfinder_mask); resultColor = resources.getColor(R.color.result_view); resultPointColor = resources.getColor(R.color.possible_result_points); possibleResultPoints = new HashSet<ResultPoint>(5); } @Override public void onDraw(Canvas canvas) { //中间的扫描框,你要修改扫描框的大小,去CameraManager里面修改 Rect frame = CameraManager.get().getFramingRect(); if (frame == null) { return; } //初始化中间线滑动的最上边和最下边 if(!isFirst){ isFirst = true; slideTop = frame.top; slideBottom = frame.bottom; } //获取屏幕的宽和高 int width = canvas.getWidth(); int height = canvas.getHeight(); paint.setColor(resultBitmap != null ? resultColor : maskColor); //画出扫描框外面的阴影部分,共四个部分,扫描框的上面到屏幕上面,扫描框的下面到屏幕下面 //扫描框的左边面到屏幕左边,扫描框的右边到屏幕右边 canvas.drawRect(0, 0, width, frame.top, paint); canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint); canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint); canvas.drawRect(0, frame.bottom + 1, width, height, paint); if (resultBitmap != null) { // Draw the opaque result bitmap over the scanning rectangle paint.setAlpha(OPAQUE); canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint); } else { //画扫描框边上的角,总共8个部分 paint.setColor(Color.GREEN); canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate, frame.top + CORNER_WIDTH, paint); canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH, frame.top + ScreenRate, paint); canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right, frame.top + CORNER_WIDTH, paint); canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right, frame.top + ScreenRate, paint); canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left + ScreenRate, frame.bottom, paint); canvas.drawRect(frame.left, frame.bottom - ScreenRate, frame.left + CORNER_WIDTH, frame.bottom, paint); canvas.drawRect(frame.right - ScreenRate, frame.bottom - CORNER_WIDTH, frame.right, frame.bottom, paint); canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom - ScreenRate, frame.right, frame.bottom, paint); //绘制中间的线,每次刷新界面,中间的线往下移动SPEEN_DISTANCE slideTop += SPEEN_DISTANCE; if(slideTop >= frame.bottom){ slideTop = frame.top; } canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH/2, frame.right - MIDDLE_LINE_PADDING,slideTop + MIDDLE_LINE_WIDTH/2, paint); //画扫描框下面的字 paint.setColor(Color.WHITE); paint.setTextSize(TEXT_SIZE * density); paint.setAlpha(0x40); paint.setTypeface(Typeface.create("System", Typeface.BOLD)); canvas.drawText(getResources().getString(R.string.scan_text), frame.left, (float) (frame.bottom + (float)TEXT_PADDING_TOP *density), paint); Collection<ResultPoint> currentPossible = possibleResultPoints; Collection<ResultPoint> currentLast = lastPossibleResultPoints; if (currentPossible.isEmpty()) { lastPossibleResultPoints = null; } else { possibleResultPoints = new HashSet<ResultPoint>(5); lastPossibleResultPoints = currentPossible; paint.setAlpha(OPAQUE); paint.setColor(resultPointColor); for (ResultPoint point : currentPossible) { canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 6.0f, paint); } } if (currentLast != null) { paint.setAlpha(OPAQUE / 2); paint.setColor(resultPointColor); for (ResultPoint point : currentLast) { canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 3.0f, paint); } } //只刷新扫描框的内容,其他地方不刷新 postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom); } } public void drawViewfinder() { resultBitmap = null; invalidate(); } /** * Draw a bitmap with the result points highlighted instead of the live * scanning display. * * @param barcode * An image of the decoded barcode. */ public void drawResultBitmap(Bitmap barcode) { resultBitmap = barcode; invalidate(); } public void addPossibleResultPoint(ResultPoint point) { possibleResultPoints.add(point); } }으로 변경하세요. 제가 올린 스캐닝 라인은 위챗에서 직접 찾아보세요.
canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH/2, frame.right - MIDDLE_LINE_PADDING,slideTop + MIDDLE_LINE_WIDTH/2, paint);스캔박스 아래에 폰트를 그리는 코드를 폰트에 따라 자동으로 중간에 정렬되도록 수정이 필요합니다. 단어가 너무 길면 처리하지 않아서 처리해야 합니다. 자동으로 래핑됩니다.
Rect lineRect = new Rect(); lineRect.left = frame.left; lineRect.right = frame.right; lineRect.top = slideTop; lineRect.bottom = slideTop + 18; canvas.drawBitmap(((BitmapDrawable)(getResources().getDrawable(R.drawable.qrcode_scan_line))).getBitmap(), null, lineRect, paint);실행 중인 인터페이스의 스크린샷을 찍으면 가운데 녹색 선이 위아래로 움직입니다. 물론 위챗의 효과와 비슷합니다. 물론 실행할 수는 있지만 그에 상응하는 권한 문제도 필요합니다.
위 내용은 모두가 Android 소프트웨어 프로그래밍을 배우는 데 도움이 되기를 바랍니다.
Google Zxing을 기반으로 다양한 QR 코드 스캔 효과를 구현하는 Android 기반 기사를 더 보려면 PHP 중국어 웹사이트를 주목하세요!