效果图:
上半部分为一个显示摄像头拍摄到的情景的窗口及一条来回循环移动的线条,下半部分为一个无功能的Btn
布局文件:
<LinearLayout 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:orientation="vertical" tools:context=".MainActivity" > <FrameLayout android:layout_width="match_parent" android:layout_height="0px" android:layout_weight=".3" > <SurfaceView android:id="@+id/preview_view" android:layout_width="fill_parent" android:layout_height="fill_parent" /> <com.example.viewtest.PicView android:id="@+id/capture_viewfinder_view" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@android:color/transparent" /> </FrameLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="0px" android:layout_weight=".7" > <Button android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="hello" /> </LinearLayout> </LinearLayout>
本布局中一个大的线性布局内嵌了帧布局和线性布局两个小布局,其中两个布局分别占屏幕的30%和70%
要实现帧布局中的效果,首先编写一个继承自View的类PicView,这个类的效果为在屏幕上画一条从左至右移动的红色线条。
code:
package com.example.viewtest; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; public class PicView extends View { private static final long ANIMATION_DELAY = 10L; private Paint paint; private int xLinePos = 0; private int canvasWidth = 0; private int canvasHeight = 0; public PicView(Context context, AttributeSet attrs) { super(context, attrs); paint = new Paint(Paint.ANTI_ALIAS_FLAG); // 开启反锯齿 paint.setColor(Color.RED); } @Override public void onDraw(Canvas canvas) { canvasWidth = canvas.getWidth(); canvasHeight = canvas.getHeight(); drawLine(canvas); postInvalidateDelayed(ANIMATION_DELAY, 0, 0, canvasWidth, canvasHeight); } private void drawLine(Canvas canvas) { // 获取屏幕的宽和高 int iLineBegin = canvasWidth / 5; int iLineEnd = canvasWidth * 4 / 5; int iFrameHigh = canvasHeight; if (++xLinePos == iLineEnd) xLinePos = iLineBegin; canvas.drawRect(xLinePos, 0, xLinePos + 1, iFrameHigh, paint); } }
类中设置刷新时间为ANIMATION_DELAY,postInvalidateDelayed函数的功能为告诉系统界面过期,需要刷新,请求系统刷新界面。在onDraw中通过postInvalidateDelayed(ANIMATION_DELAY, 0, 0, canvasWidth, canvasHeight);函数,使系统每间隔一段时间(ANIMATION_DELAY)后重复调用onDraw函数刷新屏幕( 0, 0, canvasWidth, canvasHeight),在调用onDraw的过程中又一次调用了postInvalidateDelayed,循环。。。
onDraw函数中通过drawLine函数在界面中绘制了一条线,其中每次调用时通过改变xLinePos调节线条的位置。
完成本类后在布局文件activity_main.xml中添加即可:
<com.example.viewtest.PicView android:id="@+id/capture_viewfinder_view" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@android:color/transparent" />
综上,onDraw的总体效果为间隔一段时间后向系统发送一个界面无效的消息,使界面重新调用onDraw绘制,而每次绘制时线的位置均向前移动一些,最后在视觉上达到了绘制一条从左向右重复移动的线条的效果。
摄像头效果实现:
在MainActivity中实现接口SurfaceHolder.Callback。
code:
package com.example.viewtest; import java.io.IOException; import android.app.Activity; import android.hardware.Camera; import android.os.Bundle; import android.view.Menu; import android.view.SurfaceHolder; import android.view.SurfaceView; public class MainActivity extends Activity implements SurfaceHolder.Callback { private SurfaceHolder surfaceHolder; private Camera camera; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view); surfaceHolder = surfaceView.getHolder(); surfaceHolder.addCallback(this); surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder arg0) { // TODO Auto-generated method stub camera = Camera.open(); Camera.Parameters parameters = camera.getParameters(); parameters.setPreviewSize(480, 320); // 设置 camera.setParameters(parameters); try { camera.setPreviewDisplay(surfaceHolder); } catch (IOException e) { System.out.println(e.getMessage()); } camera.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder arg0) { // TODO Auto-generated method stub if (camera != null) { camera.stopPreview(); } camera.release(); camera = null; } }
在onCreate中先获得布局文件里SurfaceView控件,之后通过SurfaceHolder对其进行控制。SurfaceHolder通过addCallBack(this)添加回调函数,
SurfaceHolder 类:
它是一个用于控制surface的接口,它提供了控制surface 的大小,格式,上面的像素,即监视其改变的。
SurfaceView的getHolder()函数可以获取SurfaceHolder对象,Surface 就在SurfaceHolder对象内。虽然Surface保存了当前窗口的像素数据,但是在使用过程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或则Canvas lockCanvas()函数来获取Canvas对象,通过在Canvas上绘制内容来修改Surface中的数据。如果Surface不可编辑或则尚未创建调用该函数会返回null,在
unlockCanvas() 和 lockCanvas()中Surface的内容是不缓存的,所以需要完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect rect)函数来指定一个rect区域,这样该区域外的内容会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。
callback接口:
只要继承SurfaceView类并实现SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了,SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,SurfaceHolder.Callback具有如下的接口:
?surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘制Surface。
?surfaceChanged(SurfaceHolder holder, int format, int width,int height):当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次。
调用surfaceChanged时系统将打开摄像头并将预览图置于指定的SurfaceView中