Android自定义View-画直线、折线拖动点可移动

Android自定义View-画直线、折线

需求:
1.在视频界面上,画出1米警戒线、2米警戒线、3米警戒线。
2.绘制车身轮廓线。
3.要求绘制的线段可调整位置。

效果如图:
效果图
解决方案:
1.自定义一个SurfaceView。
2.SurfaceView实现onDraw(),进行绘制。
3.警戒线为直线,车身轮廓线使用四个点坐标,描绘出车身轮廓。
4.拖动直线两端端点,或者车身轮廓坐标任意一点,都可改变直线。从而实现线段可随意调整位置。
5.当手指点下屏幕时,计算点击位置坐标距离哪个可移动的端点最近,如果超过一定距离则不移动端点。如果在可移动范围内,则将该坐标点设为移动结束时的x、y轴坐标点。

创建自定义View

  1. 创建自定义View
    MyBSDSurfaceView 继承SurfaceView。
  2. 实现SurfaceHolder.Callback
    是为了监听界面的变化,考虑手机上拖动画线不方便,需要加上双击切换为横屏的方法。横竖屏切换,还需要对坐标进行分辨率的转换,主要处理逻辑在surfaceChanged()中。
@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {// 检查当前屏幕方向int currentOrientation = context.getResources().getConfiguration().orientation;int orientation;if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {// 如果是横屏模式orientation = 1;Log.i("---","横屏,viewHeight = " + height + ",viewWidth = " + width);} else {orientation = 0;Log.i("---","竖屏,viewHeight = " + height + ",viewWidth = " + width);}// 第一次界面预览,横竖屏发生变化,重新计算坐标转换if (currScreenHeight == 0 || currScreenWidth == 0) {// 判断车身点和基准线点为0,则认为没有预置数据,给出一个初始画线initLine(width, height);}if (mCurrOrientation != orientation) {// 横竖屏切换了,进行坐标转换,当前屏幕分辨率 转化为 目标屏幕分辨率mCurrOrientation = orientation;float srcWidth = currScreenWidth;// 屏幕切换前的 分辨率float srcHeight = currScreenHeight;// 屏幕切换前的 分辨率pointCar1 = convertResolution(pointCar1, srcWidth, srcHeight, width, height);pointCar2 = convertResolution(pointCar2, srcWidth, srcHeight, width, height);pointCar3 = convertResolution(pointCar3, srcWidth, srcHeight, width, height);pointCar4 = convertResolution(pointCar4, srcWidth, srcHeight, width, height);//  高危险线highStartPoint = convertResolution(highStartPoint, srcWidth, srcHeight, width, height);highEndPoint = convertResolution(highEndPoint, srcWidth, srcHeight, width, height);//  中危险线midStartPoint = convertResolution(midStartPoint, srcWidth, srcHeight, width, height);midEndPoint = convertResolution(midEndPoint, srcWidth, srcHeight, width, height);//  低危险线lowStartPoint = convertResolution(lowStartPoint, srcWidth, srcHeight, width, height);lowEndPoint = convertResolution(lowEndPoint, srcWidth, srcHeight, width, height);}currScreenWidth = width;currScreenHeight = height;invalidate();}

这里有处需要注意,那就是横竖屏切换时,执行了surfaceCreated(),SurfaceView被重新创建了,需要在AndroidManifest文件对应的activity中加上配置。否则每次切换横竖屏currScreenHeight、currScreenWidth都是0。

android:configChanges="orientation|screenSize|screenLayout"

android:configChanges=“orientation|screenSize|screenLayout” 是一个 Android 配置变化属性,它用于应对屏幕旋转和尺寸变化时的问题。

这个属性的值是一个字符串,包含了应用需要处理的配置变化类型。在这个例子中,应用会处理以下三种类型的配置变化:

orientation:屏幕方向的变化,即从竖屏到横屏或从横屏到竖屏。
screenSize:屏幕大小的变化,例如从全屏模式到非全屏模式,或者从非全屏模式到全屏模式。
screenLayout:这是一种特殊的屏幕大小变化,当屏幕的宽高比发生改变时会触发。例如,当一个设备从横向模式切换到纵向模式时,或者反过来。
当这个属性被设置后,系统不会自动重启应用或销毁活动(即不会重新创建活动),而是会调用活动的 onConfigurationChanged() 方法来通知配置已经改变。这样,应用就可以在那个方法中处理配置变化,而不是重新创建活动。

  1. 实现View.OnTouchListener的监听事件
    监听屏幕的点击事件,在onTouch中处理屏幕的点击事件。并实现双击屏幕切换横屏,需要一个外部监听doubleClickListener将双击事件传递给上层,进行切换界面后的逻辑处理。

    ACTION_DOWN:屏幕被点击时,获取当前点击点坐标。并计算该点距离初始点位哪个比较近,并将这个点位设置成可移动的点。
    ACTION_MOVE:手指在屏幕上移动事件。将移动坐标赋值给moveLine这样会有直线的实时变化。
    ACTION_UP、ACTION_CANCEL:结束移动。

@Overridepublic boolean onTouch(View v,MotionEvent event) {int action = event.getAction();float x = event.getX();float y = event.getY();Point touchPoint = new Point(x, y);switch (action) {case MotionEvent.ACTION_DOWN:// 找出点击点,距离最近的 pointcurrStates = setMoveLine(touchPoint);Log.i("---", "ACTION_DOWN : (" + touchPoint.x + "," + touchPoint.y + ")");long clickTime = System.currentTimeMillis();if (clickTime - mLastClickTime < DOUBLE_CLICK_TIME_DELTA) {doubleClickListener.doubleClick();Log.i("---", "doubleClickListener");}mLastClickTime = clickTime;return true;case MotionEvent.ACTION_MOVE:if (isDragging && movingPoint != null) {movingPoint.x = (int) event.getX();movingPoint.y = (int) event.getY();invalidate();}Log.i("---", "ACTION_MOVE : (" + touchPoint.x + "," + touchPoint.y + ")");return true;case MotionEvent.ACTION_UP:isDragging = false;movingPoint = null;Log.i("---", "ACTION_UP : (" + touchPoint.x + "," + touchPoint.y + ")");return true;case MotionEvent.ACTION_CANCEL:isDragging = false;movingPoint = null;Log.i("---", "ACTION_CANCEL : (" + touchPoint.x + "," + touchPoint.y + ")");return true;}return false;}
  1. 绘制点位连接成线
    注意SurfaceView中是阻止了其自身的绘制操作的,需要在初始化时加上设置setWillNotDraw(false); 不在阻止界面绘制。
    初始化一个画笔:
	// 设置画笔属性paint.setColor(Color.RED); // 设置直线颜色为红色paint.setStrokeWidth(4);paint.setStyle(Paint.Style.STROKE);

在onDraw()中实现绘制

	@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 画三条直线path.reset();paint.setColor(Color.GREEN); // 设置直线颜色为绿色canvas.drawLine(lowStartPoint.x, lowStartPoint.y, lowEndPoint.x, lowEndPoint.y, paint);path.reset();paint.setColor(Color.YELLOW); // 设置直线颜色为黄色canvas.drawLine(midStartPoint.x, midStartPoint.y, midEndPoint.x, midEndPoint.y, paint);canvas.drawPath(path, paint); // 画直线path.reset();paint.setColor(Color.RED); // 设置直线颜色为红色canvas.drawLine(highStartPoint.x, highStartPoint.y, highEndPoint.x, highEndPoint.y, paint);canvas.drawPath(path, paint); // 画直线// 画折线path.reset();paint.setColor(Color.BLUE); // 设置折线颜色为蓝色paint.setStyle(Paint.Style.FILL);// 设置圆点实心canvas.drawCircle(pointCar1.x, pointCar1.y, radius, paint);canvas.drawCircle(pointCar2.x, pointCar2.y, radius, paint);canvas.drawCircle(pointCar3.x, pointCar3.y, radius, paint);canvas.drawCircle(pointCar4.x, pointCar4.y, radius, paint);canvas.drawLine(pointCar1.x, pointCar1.y, pointCar2.x, pointCar2.y, paint);canvas.drawLine(pointCar2.x, pointCar2.y, pointCar3.x, pointCar3.y, paint);canvas.drawLine(pointCar3.x, pointCar3.y, pointCar4.x, pointCar4.y, paint);}

自定义View使用

  1. 创建布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><com.tiamaes.test.MyBSDSurfaceViewandroid:id="@+id/myBSDSurfaceView"android:layout_width="match_parent"android:layout_height="0dp"android:visibility="visible"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintDimensionRatio="h,16:9"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
  1. Activity调用

public class MainActivity extends AppCompatActivity {MyBSDSurfaceView myBSDSurfaceView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);myBSDSurfaceView = findViewById(R.id.myBSDSurfaceView);myBSDSurfaceView.setDoubleClickListener(new MyBSDSurfaceView.DoubleClickListener() {@Overridepublic void doubleClick() {if (!isLand()) {// 横屏showFullScreenMode();} else {// 竖屏showPortraitMode();}}});}/*** 检查屏幕是否为横屏* @return*/private boolean isLand() {return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;}/*** 展示横屏*/private void showFullScreenMode() {setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);if (getSupportActionBar() != null) {getSupportActionBar().hide();}}/*** 展示竖屏*/private void showPortraitMode() {setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);if (getSupportActionBar() != null) {getSupportActionBar().show();}}@Overridepublic void onConfigurationChanged(Configuration newConfig) {if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {//设置横屏的UILog.d("---", "onConfigurationChanged ORIENTATION_LANDSCAPE");//set full screen modeint uiOptions = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;getWindow().getDecorView().setSystemUiVisibility(uiOptions);getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);WindowManager.LayoutParams layoutParams = getWindow().getAttributes();layoutParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;getWindow().setAttributes(layoutParams);} else {//设置竖屏的UILog.d("---", "onConfigurationChanged ORIENTATION_PORTRAIT");//exit from full screengetWindow().getDecorView().setSystemUiVisibility(View.VISIBLE);WindowManager.LayoutParams attrs = getWindow().getAttributes();attrs.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);getWindow().setAttributes(attrs);}super.onConfigurationChanged(newConfig);}
}

完整工程代码:demo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/209372.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python查看目录属性

os.chown(path, uid, gid)

【Proteus仿真】【STM32单片机】篮球比赛计分器

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用声光报警模块、动态数码管模块、按键模块等。 主要功能&#xff1a; 系统运行后&#xff0c;数码管显示比赛时间和AB队得分&#xff1b;系统还…

内衣洗衣机哪些品牌质量好实惠?小型洗衣机全自动

现在洗内衣内裤也是一件较麻烦的事情了&#xff0c;在清洗过程中还要用热水杀菌&#xff0c;还要确保洗衣液是否有冲洗干净&#xff0c;还要防止细菌的滋生等等&#xff0c;所以入手一款小型的烘洗全套的内衣洗衣机是非常有必要的&#xff0c;专门的内衣洗衣机可以最大程度减少…

畅谈Linux在小型微型企业中的应用

在这篇文章里我们讨论和畅谈一下linux系统在小微型企业中的应用&#xff0c;为什么会写这篇文章呢&#xff1f;因为在平时的工作中&#xff0c;认识的一些做小微型企业的朋友&#xff0c;他们经常找我咨询或是去解决一些平时工作中的IT相关的问题&#xff0c;那么小微型企业中的…

比例减压阀放大器选型

控制阀型如比例插装阀、比例方向阀、比例压力阀、比例流量阀、比例叠加阀等&#xff0c;安装方式有插式及导轨卡槽式&#xff0c;输入指令可选0-10V、4-20mA、10V、0-5V&#xff0c;输出电流可选最大3A&#xff0c;适用各大品牌不带电反馈常规比例阀匹配度&#xff0c;控制比例…

使用Pytorch从零开始构建DCGAN

在本文中&#xff0c;我们将深入研究生成建模的世界&#xff0c;并使用流行的 PyTorch 框架探索 DCGAN&#xff08;生成对抗网络 (GAN) 的一种变体&#xff09;的实现。具体来说&#xff0c;我们将使用 CelebA 数据集&#xff08;名人面部图像的集合&#xff09;来生成逼真的合…

振南技术干货集:制冷设备大型IoT监测项目研发纪实(2)

注解目录 1.制冷设备的监测迫在眉睫 1.1 冷食的利润贡献 1.2 冷设监测系统的困难 &#xff08;制冷设备对于便利店为何如何重要&#xff1f;了解一下你所不知道的便利店和新零售行业。关于电力线载波通信的论战。&#xff09; 2、电路设计 2.1 防护电路 2.1.1 强电防护 …

《DApp开发:开启全新数字时代篇章》

随着区块链技术的日益成熟&#xff0c;去中心化应用&#xff08;DApp&#xff09;逐渐成为数字世界的新焦点。在这个充满无限可能的全新领域&#xff0c;DApp开发为创新者们提供了开启数字时代新篇章的钥匙。 一、DApp&#xff1a;区块链创新成果 DApp是建立在区块链技术基础之…

2023年度openGauss标杆应用实践案例征集

标杆应用实践案例征集 2023 openGauss 数据库作为企业IT系统的核心组成部分&#xff0c;是数字基础设施建设的关键&#xff0c;是实现数据安全稳定的保障。openGauss顺应开源发展趋势&#xff0c;强化核心技术突破&#xff0c;着力打造自主根社区&#xff0c;携手产业伙伴共同…

如何写好科研论文

写好科研论文需要遵循以下步骤&#xff1a; 确定研究主题和目标&#xff1a;在开始撰写论文之前&#xff0c;你需要明确你的研究主题和目标。这有助于你更好地组织论文的内容&#xff0c;并确保你的论文能够准确地传达你的研究成果。做好文献调研&#xff1a;在撰写论文之前&a…

关于数据摆渡 你关心的5个问题都在这儿!

数据摆渡&#xff0c;这个词语的概念源自于网络隔离和数据交换的场景和需求。不管是物理隔离、协议隔离、应用隔离还是逻辑隔离&#xff0c;最终目的都是为了保护内部核心数据的安全。而隔离之后&#xff0c;又必然会存在文件交换的需求。 传统的跨网数据摆渡方式经历了从人工U…

postman定义公共函数这样写,测试组长直呼牛逼!!!

postman定义公共函数 在postman中&#xff0c;如下面的代码&#xff1a; 1、返回元素是否与预期值一致 var assertEqual(name,actual,expected)>{tests[${name}&#xff1a;实际结果&#xff1a; ${actual} &#xff0c; 期望结果&#xff1a;${expected}]actualexpected…