自定义控件Camera+Matrix实现3D旋转及百叶窗效果(雷惊风)

本文主要是介绍自定义控件Camera+Matrix实现3D旋转及百叶窗效果(雷惊风),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  上篇文章说到了用ViewPager实现3D旋转效果,本篇文章说一下自定义View+Camera+Matrix来实现更炫酷一点的效果,3D及百叶窗效果,其实还可以实现许多其他的效果。最后会将实例代码附在文章最后。先看一下最终实现的一个效果:


这里说的Camera并不是相机,而是android.graphics.Camera类,它内部包含一个Matrix,它的源码很简单,也就不到200行,可以看一下:

/** Copyright (C) 2006 The Android Open Source Project** 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**      http://www.apache.org/licenses/LICENSE-2.0** 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 android.graphics;/*** A camera instance can be used to compute 3D transformations and* generate a matrix that can be applied, for instance, on a* {@link Canvas}.*/
public class Camera {private Matrix mMatrix;/*** Creates a new camera, with empty transformations.*/public Camera() {nativeConstructor();}/*** Saves the camera state. Each save should be balanced* with a call to {@link #restore()}.* * @see #save() */public native void save();/*** Restores the saved state, if any.* * @see #restore() */public native void restore();/*** Applies a translation transform on all three axis.* * @param x The distance to translate by on the X axis* @param y The distance to translate by on the Y axis* @param z The distance to translate by on the Z axis*/public native void translate(float x, float y, float z);/*** Applies a rotation transform around the X axis.* * @param deg The angle of rotation around the X axis, in degrees* * @see #rotateY(float)* @see #rotateZ(float)* @see #rotate(float, float, float)*/public native void rotateX(float deg);/*** Applies a rotation transform around the Y axis.* * @param deg The angle of rotation around the Y axis, in degrees* * @see #rotateX(float)* @see #rotateZ(float)* @see #rotate(float, float, float) */public native void rotateY(float deg);/*** Applies a rotation transform around the Z axis.* * @param deg The angle of rotation around the Z axis, in degrees* * @see #rotateX(float)* @see #rotateY(float)* @see #rotate(float, float, float)*/    public native void rotateZ(float deg);/*** Applies a rotation transform around all three axis.* * @param x The angle of rotation around the X axis, in degrees* @param y The angle of rotation around the Y axis, in degrees* @param z The angle of rotation around the Z axis, in degrees* * @see #rotateX(float)* @see #rotateY(float)* @see #rotateZ(float)*/public native void rotate(float x, float y, float z);/*** Gets the x location of the camera.** @see #setLocation(float, float, float)*/public native float getLocationX();/*** Gets the y location of the camera.** @see #setLocation(float, float, float)*/public native float getLocationY();/*** Gets the z location of the camera.** @see #setLocation(float, float, float)*/public native float getLocationZ();/*** Sets the location of the camera. The default location is set at* 0, 0, -8.* * @param x The x location of the camera* @param y The y location of the camera* @param z The z location of the camera*/public native void setLocation(float x, float y, float z);/*** Computes the matrix corresponding to the current transformation* and copies it to the supplied matrix object.* * @param matrix The matrix to copy the current transforms into*/public void getMatrix(Matrix matrix) {nativeGetMatrix(matrix.native_instance);}/*** Computes the matrix corresponding to the current transformation* and applies it to the specified Canvas.* * @param canvas The Canvas to set the transform matrix onto*/public void applyToCanvas(Canvas canvas) {if (canvas.isHardwareAccelerated()) {if (mMatrix == null) mMatrix = new Matrix();getMatrix(mMatrix);canvas.concat(mMatrix);} else {nativeApplyToCanvas(canvas.getNativeCanvasWrapper());}}public native float dotWithNormal(float dx, float dy, float dz);protected void finalize() throws Throwable {try {nativeDestructor();native_instance = 0;} finally {super.finalize();}}private native void nativeConstructor();private native void nativeDestructor();private native void nativeGetMatrix(long native_matrix);private native void nativeApplyToCanvas(long native_canvas);long native_instance;
}

可以看到在它的内部,有一些翻转平移的native方法,看名字就知道它的意思,我们就用这些方法来实现我们想要的效果。我们开始撸码吧。

首先我们先来实现我们的2D,3D效果,同样想来看一下效果:


创建一个类继承我们的View类,实现构造方法在构造方法中初始化数据,看一下我们要想实现我们的功能需要的成员变量:

private int mViewWidth, mViewHeight;//My3DView的宽高;
private Paint mPaint;//在Canvas上绘制图片时用到的画笔;
private Camera mCamera;//实现旋转的Camera;
private Matrix matrix;//实现移动的matrix;
private float mRotateDegree = 0;//当前动画旋转的程度值;
private float mAxisX = 0, mAxisY = 0;//X方向旋转轴   Y方向旋转轴
private int mPartNumber = 1;//在百叶窗中每张图片需要分成的份数;
private List<Bitmap> bitmapList;//存放所有要展示的图片的List;
private Bitmap[][] bitmaps;//百叶窗中每张图片被分成的小图片;
private int direction = 1;//滚动方向:1竖直方向 其他为水平方向
int averageWidth = 0, averageHeight = 0;//分成小图片的平均宽高;private RollMode rollMode = RollMode.Jalousie;//默认滚动模式为百叶窗;
private int preIndex = 0, currIndex = 0, nextIndex = 0;//当前展示的图片下标及前一张后一张的下标;
private ValueAnimator valueAnimator;//属性动画;
private int rollDuration = 1 * 1000;//动画执行时间;
//正在翻转
private boolean rolling;//是否正在旋转中;//滚动模式
public enum RollMode {
//  没有效果 3D整体滚动 百叶窗Roll2D, Whole3D, Jalousie;
}

  上边每一个属性所代表的意思都已经添加了注释,这里滚动模式用到了enum类来表示。因为我们在绘制时会用到Paint、Matrix及Camera,所以我们需要在构造方法中初始化我们的工具类,看一下源码:

public My3DView(Context context) {this(context, null);
}public My3DView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);
}public My3DView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();
}/*** 初始化工作;*/
private void init() {bitmapList = new ArrayList<>();matrix = new Matrix();mCamera = new Camera();mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}

我们需要对外开放一些设置项以便可以从外部设置我们的View属性,比如我们添加要执行动画的Bitmap、动画执行的方向、在百叶窗动画中每张图片需要被分割的分数、动画执行样式(2D、3D及百叶窗),动画执行的程度(执行到什么程度了)等等信息,看一下源码对外提供的方法:

/*** 设置滚动方向;*/
public void setDirection(int directionType) {direction = directionType;
}/*** 添加图片;** @param bitmap*/
public void addBitmap(Bitmap bitmap) {bitmapList.add(bitmap);
}/*** 设置分割成的分数;** @param num*/
public void setPartitionNums(int num) {mPartNumber = num;partitionBitmaps();
}/*** 设置滚动样式;** @param type*/
public void set3DType(RollMode type) {rollMode = type;
}/*** 设置当前旋转程度;** @param degreeValue*/
public void setDegreeValue(float degreeValue) {this.mRotateDegree = degreeValue;if (direction != 1)mAxisX = degreeValue / (float) (rollMode == RollMode.Jalousie ? 180 : 90) * mViewWidth;elsemAxisY = degreeValue / (float) (rollMode == RollMode.Jalousie ? 180 : 90) * mViewHeight;invalidate();
}

这里在设置百叶窗效果分割图片份数的时候有一个partitionBitmaps()方法,在这个方法中我们对所有添加进来的图片进行一个分隔操作,后面会分析,然后就是在设置动画转动程度的方法中我们通过不同方向计算了当前degreeValue值对比整个动画旋转角度的mAxisX及mAxisY,当是百叶窗效果的时候,我们整个动画是旋转了180度的,而3D效果则只需要旋转90度。我们这个degreeValue如果是百叶窗取值范围会是0-180度之间,3D取值范围会在0-90度之间。接下来看一下我们在onMeasure()方法中做了哪些操作:

//获取当前View宽高,根据宽高缩放图片;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);mViewWidth = getMeasuredWidth();mViewHeight = getMeasuredHeight();if (mViewWidth != 0 && mViewHeight != 0) {for (int i = 0; i < bitmapList.size(); i++) {bitmapList.set(i, zoomBitmap(bitmapList.get(i)));}}//设置当前图片的上一张与下一张;setBitmapIndex();//需要分割;if (rollMode == Jalousie)partitionBitmaps();
}

我们调用super.onMeasure()方法来进行测量自身,获取测量的宽高,如果View的宽高都不等于0我们通过for循环去处理我们通过addBitmap()方法添加进来的图片进行缩放处理,看一下缩放代码:

/*** 缩放图片;** @param bit* @return*/
private Bitmap zoomBitmap(Bitmap bit) {if (bit == null)return null;int bitmapW = bit.getWidth();int bitmapH = bit.getHeight();float scaleW = (float) mViewWidth / bitmapW;float scaleH = (float) mViewHeight / bitmapH;Matrix matrix = new Matrix();matrix.postScale(scaleW, scaleH);return Bitmap.createBitmap(bit, 0, 0, bitmapW, bitmapH, matrix, false);
}

就是根据当前View的宽高与我们设置进来的图片的宽高比通过matrix来缩放bitmap。替换掉原来的图片。然后设置我们的上一张图片与下一张图片,代码如下:

private void setBitmapIndex() {preIndex = currIndex - 1;if (preIndex < 0) {preIndex = bitmapList.size() - 1;}nextIndex = currIndex + 1;if (nextIndex > bitmapList.size() - 1)nextIndex = 0;
}

也很简单,就是当向前越界时置为最后一张,向后越界时置为第一张,实现循环动画效果。接下来判断是不是百叶窗效果,如果是,则需要对图片进行分割,之前在设置分割数量时也调用了这个方法,下边来看一下具体实现:

/*** 处理分割bitmap;*/
private void partitionBitmaps() {if (bitmapList == null || bitmapList.size() == 0) return;//下边在分割时会用到viewWidth与viewHeigth,所以需要在这里进行判断;if (mViewWidth == 0 || mViewHeight == 0) return;averageHeight = mViewHeight / mPartNumber;averageWidth = mViewWidth / mPartNumber;bitmaps = new Bitmap[bitmapList.size()][mPartNumber];for (int i = 0; i < bitmapList.size(); i++) {Bitmap bit = bitmapList.get(i);for (int j = 0; j < mPartNumber; j++) {Bitmap bitDebris;//百叶窗;if (rollMode == RollMode.Jalousie) {//如果是竖向的百叶窗,需要竖向分割,最终绕Y轴转动;if (direction == 1) {bitDebris = Bitmap.createBitmap(bit, j * averageWidth, 0, averageWidth, mViewHeight);} elsebitDebris = Bitmap.createBitmap(bit, 0, j * averageHeight, mViewWidth, averageHeight);} else {if (direction == 1)bitDebris = Bitmap.createBitmap(bit, j * averageWidth, 0, averageWidth, mViewHeight);elsebitDebris = Bitmap.createBitmap(bit, 0, j * averageHeight, mViewWidth, averageHeight);}bitmaps[i][j] = bitDebris;}}setBitmapIndex();}

判断是否存在需要分割的图片并且当前View宽高都大于0,通过要分割的分数与当前View的宽高得到被分割的每一份图片宽高averageWidth、averageHeight

,然后创建我们的bitmaps二维数组存放我们每一个被分割的图片,二维数组的第一项表示被分割的是list中的第几张图片,二维数组的第二项表示当前图片被分割成的每一个小图片。这里为了条理更加清晰,分为了是百叶窗效果与不是百叶窗效果,对于我们的功能只需要看百叶窗逻辑就好。还处理了是横向滚动还是竖向滚动的分割。

图片已经准备好了,那么我们的重点就来了,看一下我们最终的onDraw()方法吧:

@Override
public void draw(Canvas canvas) {super.draw(canvas);switch (rollMode) {case Whole3D:case Roll2D:draw3D(canvas);break;case Jalousie:drawJalousie(canvas);break;}
}

这里2D、3D效果统一实现了,因为他们两个其实是一样的,只不过一个有转动效果,一个没有。看一下我们的具体实现draw3D(canvas)吧:

/*** 绘制3D效果;** @param canvas*/
private void draw3D(Canvas canvas) {//初始Bitmap currBitmap = bitmapList.get(currIndex);Bitmap nextBitmap = bitmapList.get(nextIndex);canvas.save();//竖向转动if (direction == 1) {mCamera.save();//我们首先设置当前正在显示的图片的转动动画效果,如果是3D效果,设置旋转角度,//因为竖向转动,我们需要绕X轴旋转,rotateX(float deg)中deg参数为正时,会向屏幕外转动,// 当前需要向屏幕内测转动当前显示的图片,所以设置为-mRotateDegree;2D效果没有转动效果,所以设置转动效果为0;if (rollMode == Whole3D)mCamera.rotateX(-mRotateDegree);elsemCamera.rotateX(0);//获取camera中的matrix;mCamera.getMatrix(matrix);//不在使用camera需要复原;mCamera.restore();//使用matrix前乘移动,也就是在Camera转动前移动matrix,因为Camera转动是以View左上角为原点转动的,// 我们旋转时是以当前图片的上边缘为旋转轴来向屏幕内正着转动,所以在转动前我们需要先将矩阵向左移动View的1/2宽度,// 因为是以上边缘为轴,所以高度不用动;当旋转完后,我们需要将camera所在矩阵还原到原来位置,// 所以我们需要向右移动1/2View宽度;因为在旋转的过程中我们当前的View需要有一下向下移动的程度,因为之前我们已经计算好,// 这个度会随着我们的滚动而逐渐变大,也就是我们之前算好的mAxisY,最后在我们的canvas上边绘制我们当前图片就好。matrix.preTranslate(-mViewWidth / 2, 0);matrix.postTranslate(mViewWidth / 2, mAxisY);canvas.drawBitmap(currBitmap, matrix, mPaint);matrix.reset();mCamera.save();//如果是3D效果,因为竖向滚动,下一张图片,是从90度开始一直旋转到0度的一个过程,所以这里需要90-mRotateDegree;if (rollMode == Whole3D)mCamera.rotateX(90 - mRotateDegree);elsemCamera.rotateX(0);mCamera.getMatrix(matrix);mCamera.restore();//View宽度针对X轴的移动与上边是一样的,看一下高度,因为我们将要出现的这张图片是从当前View的上部分出来的,// 所以我们需要现将即将显示的图片移动到-mViewHeight的高度,然后在一点一点向下移动,最终到达当前View的高度;//最后绘制我们即将显示的图片;matrix.preTranslate(-mViewWidth / 2, -mViewHeight);//0matrix.postTranslate(mViewWidth / 2, mAxisY);//mViewHeight / 2 + mAxisYcanvas.drawBitmap(nextBitmap, matrix, mPaint);} else {//以下为横向滚动的2D、3D效果实现,与竖向滚动原理一样;mCamera.save();if (rollMode == Whole3D)mCamera.rotateY(mRotateDegree);elsemCamera.rotateY(0);mCamera.getMatrix(matrix);mCamera.restore();matrix.preTranslate(0, -mViewHeight / 2);matrix.postTranslate(mAxisX, mViewHeight / 2);canvas.drawBitmap(currBitmap, matrix, mPaint);mCamera.save();if (rollMode == Whole3D)mCamera.rotateY(-(90 - mRotateDegree));elsemCamera.rotateY(0);mCamera.getMatrix(matrix);mCamera.restore();matrix.preTranslate(-mViewWidth, -mViewHeight / 2);matrix.postTranslate(mAxisX, mViewHeight / 2);canvas.drawBitmap(nextBitmap, matrix, mPaint);}canvas.restore();
}

我们首先获取了当前显示的图片与下一张将要显示的图片,判断方向,重点是根据不同方向通过Camera与Matrix实现旋转与平移,重点逻辑的代码已经加好注释,这里就不做过多解释,到这里我们实现2D、3D功能的主要工作就算基本完成了,接下来,我们在MainActivity的布局文件中定义我们的自定义View,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:gravity="center_horizontal"><com.jason.mycamera3ddemo.My3DViewandroid:id="@+id/my3dView"android:layout_width="250dp"android:layout_height="150dp"/><SeekBarandroid:id="@+id/seekbar"android:layout_width="match_parent"android:layout_height="50dp"/><Buttonandroid:layout_width="300dp"android:layout_height="50dp"android:onClick="changeDirection"android:text="修改方向" /><Buttonandroid:layout_width="300dp"android:layout_height="50dp"android:onClick="changeType"android:text="修改样式" /><Buttonandroid:layout_width="300dp"android:layout_height="50dp"android:onClick="toNext"android:text="下一张图"/></LinearLayout>

然后在MainActivity中简单处理就可:

public class MainActivity extends AppCompatActivity {My3DView my3dView;SeekBar seekbar;List<My3DView.RollMode> modeList = new ArrayList<>();int modeCount = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);saveMode();my3dView = (My3DView) findViewById(R.id.my3dView);seekbar = (SeekBar) findViewById(R.id.seekbar);Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.a);Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.b);Bitmap bitmap3 = BitmapFactory.decodeResource(getResources(), R.drawable.c);Bitmap bitmap4 = BitmapFactory.decodeResource(getResources(), R.drawable.d);Bitmap bitmap5 = BitmapFactory.decodeResource(getResources(), R.drawable.e);my3dView.addBitmap(bitmap1);my3dView.addBitmap(bitmap2);my3dView.addBitmap(bitmap3);my3dView.addBitmap(bitmap4);my3dView.addBitmap(bitmap5);my3dView.setDirection(0);my3dView.setPartitionNums(5);
//        my3dView.set3DType(My3DView.RollMode.Jalousie);
//        seekbar.setMax(180);
//        my3dView.set3DType(My3DView.RollMode.Whole3D);
//        seekbar.setMax(90);my3dView.set3DType(My3DView.RollMode.Roll2D);seekbar.setMax(90);seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {my3dView.setDegreeValue(progress);}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {}});}private void saveMode() {modeList.add(My3DView.RollMode.Roll2D);modeList.add(My3DView.RollMode.Whole3D);modeList.add(My3DView.RollMode.Jalousie);
//        modeList.add(My3DView.RollMode.Jalousie);
//        modeList.add(My3DView.RollMode.Jalousie);}public void toNext(View view) {}public void changeDirection(View view) {my3dView.changeDirection();}public void changeType(View view) {my3dView.changeModeType(modeList.get(modeCount));modeCount+=1;if (modeCount > modeList.size()-1)modeCount = 0;}
}

这样我们的2D、3D效果就完成了。接下来看一下百叶窗效果的实现,也就是在自定义View的onDraw(Canvas canvas)方法中调用的drawJalousie()方法,先看效果吧:


上实现逻辑吧:

/*** 绘制百叶窗效果;** @param canvas*/
private void drawJalousie(Canvas canvas) {canvas.save();for (int i = 0; i < mPartNumber; i++) {//获取当前显示与即将显示的图片的每一部分图片Bitmap currBitmap = bitmaps[currIndex][i];Bitmap nextBitmap = bitmaps[nextIndex][i];//小于90度只操作当前显示的图片;if (mRotateDegree < 90) {//纵向小于90度,因为没有显示出即将显示的图片,所以只需旋转第一张图片;if (direction == 1) {mCamera.save();//竖向的时候,为竖向分割,绕Y轴旋转;mCamera.rotateY(mRotateDegree);mCamera.getMatrix(matrix);mCamera.restore();//matrix向左向上是负的;因为旋转是按自身的中轴转动,所以首先移动自己的中心点到Camera原点,//最后移动恢复原位置,为了并排显示,需要加上每一份的宽度;最终绘制当前显示图片的每一小部分图片;matrix.preTranslate(-currBitmap.getWidth() / 2, -currBitmap.getHeight() / 2);matrix.postTranslate(currBitmap.getWidth() / 2 + i * averageWidth, currBitmap.getHeight() / 2);canvas.drawBitmap(currBitmap, matrix, mPaint);} else {mCamera.save();mCamera.rotateX(mRotateDegree);mCamera.getMatrix(matrix);mCamera.restore();//matrix向左向上是负的;matrix.preTranslate(-currBitmap.getWidth() / 2, -currBitmap.getHeight() / 2);matrix.postTranslate(currBitmap.getWidth() / 2, currBitmap.getHeight() / 2 + i * averageHeight);canvas.drawBitmap(currBitmap, matrix, mPaint);}} else {//当大于90度时,因为第一张图片已经不在显示,所以不用再管,只需旋转将要显示的图片就好;if (direction == 1) {mCamera.save();//从180最终旋转到0度;mCamera.rotateY(180 - mRotateDegree);mCamera.getMatrix(matrix);mCamera.restore();//matrix向左向上是负的;matrix.preTranslate(-nextBitmap.getWidth() / 2, -nextBitmap.getHeight() / 2);matrix.postTranslate(nextBitmap.getWidth() / 2 + i * averageWidth, nextBitmap.getHeight() / 2);canvas.drawBitmap(nextBitmap, matrix, mPaint);} else {mCamera.save();mCamera.rotateX(180 - mRotateDegree);mCamera.getMatrix(matrix);mCamera.restore();//matrix向左向上是负的;matrix.preTranslate(-nextBitmap.getWidth() / 2, -nextBitmap.getHeight() / 2);matrix.postTranslate(nextBitmap.getWidth() / 2, nextBitmap.getHeight() / 2 + i * averageHeight);canvas.drawBitmap(nextBitmap, matrix, mPaint);}}}canvas.restore();}

这样便实现了上面的百叶窗效果,到这里,我们想要实现的所有主功能就都有了,那么大家看一开始的图片,有一个下一站图的按钮,点击会执行整个动画过程,而不需要滑动我们的Seekbar,这个功能也好实现,这里顺便说一下,在MainActivity中实现方法:

public void toNext(View view) {my3dView.toNext();seekbar.setProgress(0);
}
在自定义View中添加新的方法toNext():
public void toNext() {if (rolling) return;if (rollMode == Jalousie) {valueAnimator = ValueAnimator.ofFloat(0, 180);} else if (rollMode == Whole3D || rollMode == Roll2D) {valueAnimator = ValueAnimator.ofFloat(0, 90);}rolling = true;valueAnimator.setDuration(rollDuration);valueAnimator.addUpdateListener(aniUpdateListener);valueAnimator.addListener(aniEndListener);valueAnimator.start();}

这里就用到了属性动画ValueAnimator,并添加了两个监听,看一下监听的实现:

private ValueAnimator.AnimatorUpdateListener aniUpdateListener = new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {setDegreeValue((Float) animation.getAnimatedValue());}
};
private AnimatorListenerAdapter aniEndListener = new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);currIndex++;if (currIndex > bitmapList.size() - 1)currIndex = 0;setBitmapIndex();setDegreeValue(0);rolling = false;}
};

其实就是一个动画执行过程中,不断的去更改我们的degreeValue值去调用重绘方法间接调用onDraw()方法进行不停的绘制,达到进行整个动画的目的,最后一个监听是监听动画结束后的初始化工作,也很简单。这样就完成了我们的功能,在运行看一下效果吧:


好了,我们所有的功能都完成了,点击下载源码。

这篇关于自定义控件Camera+Matrix实现3D旋转及百叶窗效果(雷惊风)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/216457

相关文章

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

防近视护眼台灯什么牌子好?五款防近视效果好的护眼台灯推荐

在家里,灯具是属于离不开的家具,每个大大小小的地方都需要的照亮,所以一盏好灯是必不可少的,每个发挥着作用。而护眼台灯就起了一个保护眼睛,预防近视的作用。可以保护我们在学习,阅读的时候提供一个合适的光线环境,保护我们的眼睛。防近视护眼台灯什么牌子好?那我们怎么选择一个优秀的护眼台灯也是很重要,才能起到最大的护眼效果。下面五款防近视效果好的护眼台灯推荐: 一:六个推荐防近视效果好的护眼台灯的