用Android studio写构建一个画板的文件。包括以下功能:1、修改描边宽度;2、更改画板颜色;3、擦除;4、全部清除;5、保存;6、退出。

时间: 2023-06-20 18:04:06 浏览: 130

以下是一个简单的画板应用程序的代码。包括所需的功能:修改描边宽度、更改画板颜色、擦除、全部清除、保存、退出。

MainActivity.java:

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.io.File;
import java.io.FileOutputStream;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {

    private DrawingView drawingView;
    private SeekBar seekBar;
    private Button clearBtn, eraseBtn, saveBtn, exitBtn;
    private int strokeWidth = 4;
    private int currentColor = Color.BLACK;
    private boolean eraseMode = false;

    private static final int REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        drawingView = new DrawingView(this);
        LinearLayout layout = findViewById(R.id.layout);
        layout.addView(drawingView);

        seekBar = findViewById(R.id.seekBar);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                strokeWidth = progress;
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

        clearBtn = findViewById(R.id.clearBtn);
        clearBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                drawingView.clear();
            }
        });

        eraseBtn = findViewById(R.id.eraseBtn);
        eraseBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                eraseMode = !eraseMode;
                if (eraseMode) {
                    eraseBtn.setText(R.string.draw_mode);
                } else {
                    eraseBtn.setText(R.string.erase_mode);
                }
            }
        });

        saveBtn = findViewById(R.id.saveBtn);
        saveBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(MainActivity.this,
                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                            REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE);
                } else {
                    saveImage();
                }
            }
        });

        exitBtn = findViewById(R.id.exitBtn);
        exitBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }

    private void saveImage() {
        String fileName = UUID.randomUUID().toString() + ".png";
        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), fileName);

        try {
            FileOutputStream fos = new FileOutputStream(file);
            Bitmap bitmap = drawingView.getBitmap();
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
            fos.flush();
            fos.close();
            Toast.makeText(this, R.string.image_saved, Toast.LENGTH_SHORT).show();
            Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
            intent.setData(Uri.fromFile(file));
            sendBroadcast(intent);
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, R.string.image_save_error, Toast.LENGTH_SHORT).show();
        }
    }

    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                saveImage();
            } else {
                Toast.makeText(this, R.string.permission_denied, Toast.LENGTH_SHORT).show();
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    public class DrawingView extends View {

        private Bitmap bitmap;
        private Canvas canvas;
        private Path path;
        private Paint paint;

        public DrawingView(Context context) {
            super(context);
            bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
            canvas = new Canvas(bitmap);
            path = new Path();
            paint = new Paint();
            paint.setColor(currentColor);
            paint.setStrokeWidth(strokeWidth);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeJoin(Paint.Join.ROUND);
            paint.setStrokeCap(Paint.Cap.ROUND);
            setDrawingCacheEnabled(true);
        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            canvas = new Canvas(bitmap);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawBitmap(bitmap, 0, 0, null);
            canvas.drawPath(path, paint);
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            float x = event.getX();
            float y = event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    path.moveTo(x, y);
                    break;
                case MotionEvent.ACTION_MOVE:
                    path.lineTo(x, y);
                    break;
                case MotionEvent.ACTION_UP:
                    canvas.drawPath(path, paint);
                    path.reset();
                    break;
                default:
                    return false;
            }
            invalidate();
            return true;
        }

        public void clear() {
            canvas.drawColor(Color.WHITE);
            invalidate();
        }

        public Bitmap getBitmap() {
            return getDrawingCache();
        }

        public void setColor(int color) {
            currentColor = color;
            paint.setColor(currentColor);
        }

        public void setStrokeWidth(int width) {
            strokeWidth = width;
            paint.setStrokeWidth(strokeWidth);
        }

        public void setEraseMode(boolean erase) {
            eraseMode = erase;
            if (erase) {
                paint.setColor(Color.WHITE);
            } else {
                paint.setColor(currentColor);
            }
        }
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <SeekBar
        android:id="@+id/seekBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="20"
        android:progress="4"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/clearBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/clear"/>

        <Button
            android:id="@+id/eraseBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/erase_mode"/>

        <Button
            android:id="@+id/saveBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/save"/>

        <Button
            android:id="@+id/exitBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/exit"/>

    </LinearLayout>

</LinearLayout>

strings.xml:

<resources>
    <string name="app_name">My Drawing Board</string>
    <string name="clear">Clear</string>
    <string name="erase_mode">Erase Mode</string>
    <string name="draw_mode">Draw Mode</string>
    <string name="save">Save</string>
    <string name="exit">Exit</string>
    <string name="image_saved">Image saved successfully.</string>
    <string name="image_save_error">Error while saving image.</string>
    <string name="permission_denied">Permission denied.</string>
</resources>

在 AndroidManifest.xml 文件中添加以下权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
向AI提问 loading 发送消息图标

最新推荐

recommend-type

软件工程享元模式详解:减少对象创建优化内存占用与系统性能提升方法

内容概要:文章详细介绍了享元模式(Flyweight Pattern),一种旨在减少对象创建数量以降低内存占用、提升系统性能的结构型设计模式。文中通过共享单车系统类比,生动解释了享元模式的核心思想,并结合图形绘制系统、文本编辑器和游戏开发等具体场景,阐述了享元模式的结构与角色,包括抽象享元角色、具体享元角色、非共享具体享元角色和享元工厂角色。文章还展示了如何通过享元模式优化代码,减少内存占用,并分析了享元模式在文本处理、游戏开发和数据库连接池中的应用场景。最后,文章总结了享元模式的优点(减少内存占用、提高性能、降低资源消耗)和缺点(增加系统复杂性、分离内外状态难度大、可能延长运行时间)。 适合人群:具有一定编程基础,尤其是对面向对象设计模式感兴趣的开发人员。 使用场景及目标:① 在需要创建大量相似对象的场景中,如文本处理、游戏开发和数据库连接管理;② 目标是通过减少对象创建数量来降低内存占用,提高系统性能和资源利用率。 阅读建议:读者应结合实际项目需求,评估是否适合采用享元模式,注意区分内部状态和外部状态,并考虑系统复杂性和性能之间的平衡。在学习过程中,可以通过代码示例进行实践,加深对享元模式的理解。
recommend-type

使用 SolidWorks 设计的无人机(四轴飞行器)

无人机(四轴飞行器) 这是使用 SolidWorks 设计的四轴飞行器的详细 3D CAD 模型。该组件采用三臂结构,包含无刷电机、螺旋桨、中央框架和已安装的电池组。该结构针对轻量化性能进行了优化,展示了逼真的机械组件,例如电机支架、支撑架和模块化框架。非常适合无人机设计演示和原型设计。
recommend-type

unreal engine的C++教程

unreal engine的C++教程
recommend-type

基于Python技术的2019年毕业论文参考文献整理

基于Python技术的2019年毕业论文参考文献整理
recommend-type

ASR小助手-1.3.apk

当前所发布的全部内容源于互联网搬运整理收集,仅限于小范围内传播学习和文献参考,仅供日常使用,不得用于任何商业用途,请在下载后24小时内删除,因下载本资源造成的损失,全部由使用者本人承担!如果有侵权之处请第一时间联系我们删除。敬请谅解!
recommend-type

WPF实现左右滑动切换图片的小程序

在探讨如何利用WPF(Windows Presentation Foundation)实现滑动条更换图片的功能时,首先需要对WPF的基本概念和相关技术有所了解。 WPF是一个用于开发Windows桌面应用程序的UI框架,它允许开发者利用XAML(可扩展应用程序标记语言)创建界面,并结合.NET框架进行编程。WPF的核心优势在于其丰富的视觉效果、数据绑定能力、可扩展性和硬件加速。它支持复杂的视觉设计和丰富的交互性,非常适合进行复杂的用户界面设计。 ### 1. XAML与C#结合使用 实现WPF滑动条换图片的基本思路是,使用XAML定义界面布局,将滑动条(Slider)控件和图片显示控件(例如Image)放置于界面上,并利用C#代码实现滑动条值改变时触发的事件处理逻辑,从而达到更换图片的目的。 ### 2. 控件介绍 **Slider控件**: 在WPF中,Slider控件用于创建滑动条。它具有Minimum、Maximum、Value等属性,分别代表滑动条的最小值、最大值和当前值。通过设置这些属性,开发者可以定义滑动条的范围和用户可选择的值。 **Image控件**: Image控件用于显示图片。它有一个Source属性,可以通过设置该属性来指定显示的图片。Source属性可以接受多种类型的值,例如bitmap、png等格式的图片文件。 ### 3. 实现逻辑 要实现滑动条更换图片的功能,核心步骤如下: 1. **准备图片资源**: 将需要显示的图片放入项目的文件夹中,并在项目中建立一个图片资源列表,例如一个数组或列表,里面存放所有图片文件的相对路径或绝对路径。 2. **设置Slider控件的属性**: 需要确保Slider控件的Minimum属性设置为0,Maximum属性设置为图片数量减1(即图片索引的上限)。这样,滑动条的值就可以对应到数组索引。 3. **绑定事件处理逻辑**: 将Slider的Value属性通过数据绑定与图片索引相绑定。当滑动条的值发生变化时(即用户拖动滑动条时),会触发一个事件处理函数。 4. **图片更换逻辑**: 在事件处理函数中,根据滑动条的Value属性值来选择图片。将当前图片路径设置到Image控件的Source属性中。这里需要确保索引不会越界,即在图片总数范围内。 5. **异常处理**: 在图片路径设置之前,应进行判断,确保路径有效,避免程序因为无法找到文件而异常退出。可以进行异常捕获或者预先检查路径是否存在。 ### 4. 示例代码 以下是一个简化的C#代码示例,用于说明如何在WPF中实现滑动条更换图片的基本逻辑: ```csharp // 假设有一个图片数组 string[] imagePaths = new string[] { "image1.png", "image2.png", ... }; private void Slider_Loaded(object sender, RoutedEventArgs e) { // 与滑动条的Maximum属性绑定 this.Slider.Value = imagePaths.Length - 1; } private void Slider_SelectionChanged(object sender, SelectionChangedEventArgs e) { // 确保值在有效范围内 if (this.Slider.Value >= 0 && this.Slider.Value < imagePaths.Length) { // 设置图片源 ImageControl.Source = new BitmapImage(new Uri(imagePaths[(int)this.Slider.Value])); } else { // 处理异常情况 MessageBox.Show("图片索引超出范围"); } } ``` 在这个示例中,`Slider_Loaded`是滑动条加载完成时触发的事件处理函数,在该函数中设置了滑动条的最大值。`Slider_SelectionChanged`是滑动条值变化时触发的事件处理函数,在该函数中根据滑动条的值更换图片。 ### 5. 总结 使用WPF实现滑动条更换图片是一个结合XAML布局设计与C#后端逻辑处理的典型示例。通过理解WPF中的事件处理、数据绑定、异常处理等概念,并将这些知识应用到实际项目中,可以创建出美观且功能强大的桌面应用程序。在实际开发过程中,还需要考虑用户界面的美观性、操作的流畅性和异常的鲁棒性等因素,以提高用户体验。
recommend-type

【EasyX文本框性能提升秘籍】:专家级技巧优化交互体验

# 1. EasyX文本框概述及性能问题 在信息技术飞速发展的今天,用户界面(UI)的响应速度和视觉效果对于提升用户体验至关重要。EasyX 是一个轻量级的图形库,它提供了一系列丰富的接口以简化文本框等界面元素的创建和管理。尽管 EasyX 在易用性和轻量级方面具有优势,但其文本框组件在高性能应用场合中面临着诸多挑战,特别是当处
recommend-type

基于YOLOv8锥桶检测

<think>好的,用户想用YOLOv8实现锥桶检测。首先,我需要回忆YOLOv8的相关知识,知道它是Ultralytics维护的最新版本,相比YOLOv5可能有一些改进。用户之前可能了解过YOLOv5,现在想升级到v8,所以需要比较两者的差异,但重点还是放在实现步骤上。接下来,用户需要具体的教程,所以步骤要详细。首先,环境配置是基础,需要安装Ultralytics的库,可能还需要PyTorch和CUDA支持GPU加速。然后,数据准备是关键,用户可能需要收集锥桶的图片,标注成YOLO格式,也就是txt文件,每个对象一行,包括类别、中心坐标和宽高。这里要提醒用户注意数据集的划分,比如训练集、验证
recommend-type

多目标视频跟踪技术详解与粒子滤波应用

视频跟踪技术是计算机视觉和图像处理领域的一项核心技术,它涉及到从视频序列中自动检测和跟踪感兴趣目标(如人、车辆、动物等)。当提到“多目标跟踪”时,意味着系统可以同时跟踪视频中的多个目标,并将它们区分开来。这对于智能监控、人机交互、自动驾驶车辆等应用场景至关重要。 描述中提到的“实时效果还好”,这暗示了该视频跟踪系统具有较好的处理速度,能够快速响应视频中的变化,并且对多目标进行跟踪时的准确度和稳定性较高,这对于实际应用来说是非常重要的。实时跟踪能够为用户提供及时的反馈,这对于需要快速响应的应用场景尤为关键。 针对标签“视频跟踪 多目标”,以下是详细的知识点: 1. 多目标跟踪算法: - 目标检测:多目标跟踪的第一步是目标检测,即在视频帧中识别出所有的目标物体。常用的算法有YOLO(You Only Look Once)、SSD(Single Shot MultiBox Detector)、Faster R-CNN等。 - 跟踪算法:检测到目标后,需要使用特定算法进行跟踪。常见的跟踪算法有卡尔曼滤波、均值漂移、光流法、以及基于深度学习的方法如Siamese Networks、DeepSORT等。 - 数据关联:多目标跟踪的一个挑战是如何将一帧中的目标与之前帧中的目标正确对应,即解决数据关联问题。粒子滤波器(Particle Filter)是一种常用的解决方法。 2. 粒子滤波器(Particle Filter): 粒子滤波器是一种基于蒙特卡洛方法的递归贝叶斯滤波技术,它通过一组随机样本(粒子)来表示概率分布,每个粒子代表一个可能的系统状态。在多目标跟踪中,粒子滤波器能够根据视频帧中的观测数据来更新每个目标的状态估计。 粒子滤波器工作原理: - 初始化:为每个目标生成一组随机粒子,每个粒子代表一个可能的状态。 - 预测:根据系统的动态模型,对下一时刻每个粒子的状态进行预测。 - 更新:当新的观测数据到来时,对每个粒子的权重进行更新,权重反映了粒子代表的状态与实际观测的匹配程度。 - 重采样:根据粒子的权重进行重采样,去除权重较低的粒子,复制权重较高的粒子,从而得到新的粒子集。 - 输出:粒子集的均值或其他统计特性作为目标状态的估计。 3. 应用场景: - 智能监控:在安全监控中,需要实时跟踪视频中的人物或车辆,进行行为分析和异常检测。 - 人机交互:在增强现实或交互式游戏场景中,需要准确跟踪用户的身体部位或手部动作。 - 自动驾驶:自动驾驶车辆需要实时跟踪道路上的其他车辆、行人以及各种障碍物,以确保行车安全。 4. 技术挑战: - 目标遮挡:当目标被遮挡或部分遮挡时,正确地识别和跟踪目标变得困难。 - 目标交互:多目标之间的交互(如相交、相离)可能会对跟踪算法造成干扰。 - 算法效率:实时跟踪对算法的计算效率要求很高,需要在保持跟踪准确性的同时,降低算法的计算复杂度。 以上是基于给定文件信息的详细知识点。视频跟踪技术的多目标实时跟踪功能对众多行业提供了深远的影响,其发展也带来了对计算资源、算法优化等方面的挑战,这需要不断地研究和创新来解决。
recommend-type

【紧急!EasyX文本框问题速查手册】:快速定位与解决常见难题

# 1. EasyX文本框问题速查概述 在图形用户界面(GUI)编程中,文本框是一个基础且核心的组件,承担着与用户进行信息交互的主要任务。EasyX文本框作为一款简便的图形库,为文本交互提供了直观的解决方案。然而,在实际开发过程中,开发者经常遇到各种与文本框相关的问题,如绘制错误、布局不合理、输入处理不当等。本章将概述EasyX文本框
手机看
程序员都在用的中文IT技术交流社区

程序员都在用的中文IT技术交流社区

专业的中文 IT 技术社区,与千万技术人共成长

专业的中文 IT 技术社区,与千万技术人共成长

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

客服 返回
顶部