Android编程实现支持拖动改变位置的图片中叠加文字功能示例
发布时间 - 2026-01-10 22:41:43 点击率:次本文实例讲述了Android编程实现支持拖动改变位置的图片中叠加文字功能。分享给大家供大家参考,具体如下:

之所以做了这么一个Demo,是因为最近项目中有一个奇葩的需求:用户拍摄照片后,分享到微信的同时添加备注,想获取用户在微信的弹出框输入的内容,保存在自己的服务器上。而事实上,这个内容程序是无法获取的,因此采取了一个折衷方案,将文字直接写在图片上。
首先上Demo效果图:
功能:
1.用户自由输入内容,可手动换行,并且行满也会自动换行。
2.可拖动改变图片中文本位置(文字不会超出图片区域)。
3.点击“生成图片”按钮之后,生成一张带有文字的图片文件。
代码不多,直接全部贴上了:
Activity:
/**
* 将文字写在图片中(截图方式),支持拖动文字。<br/>
* 说明:很明显,截图方式会降低图片的质量。如果需要保持图片质量可以使用canvas的方式,将文字直接绘制在图片之上(不过,使用此方式要实现文字拖动较为复杂)。
*/
public class MainActivity extends AppCompatActivity {
//图片组件
private ImageView imageView;
//位于图片中的文本组件
private TextView tvInImage;
//图片和文本的父组件
private View containerView;
//父组件的尺寸
private float imageWidth, imageHeight, imagePositionX, imagePositionY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.image_with_text);
imageView = (ImageView) findViewById(R.id.writeText_img);
EditText editText = (EditText) findViewById(R.id.writeText_et);
tvInImage = (TextView) findViewById(R.id.writeText_image_tv);
containerView = findViewById(R.id.writeText_img_rl);
imageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
imageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
imagePositionX = imageView.getX();
imagePositionY = imageView.getY();
imageWidth = imageView.getWidth();
imageHeight = imageView.getHeight();
//设置文本大小
tvInImage.setMaxWidth((int) imageWidth);
}
});
imageView.setImageBitmap(getScaledBitmap(R.mipmap.test_img));
//输入框
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.toString().equals("")) {
tvInImage.setVisibility(View.INVISIBLE);
} else {
tvInImage.setVisibility(View.VISIBLE);
tvInImage.setText(s);
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
final GestureDetector gestureDetector = new GestureDetector(this, new SimpleGestureListenerImpl());
//移动
tvInImage.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
return true;
}
});
}
//确认,生成图片
public void confirm(View view) {
Bitmap bm = loadBitmapFromView(containerView);
String filePath = Environment.getExternalStorageDirectory() + File.separator + "image_with_text.jpg";
try {
bm.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(filePath));
Toast.makeText(this, "图片已保存至:SD卡根目录/image_with_text.jpg", Toast.LENGTH_LONG).show();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
//以图片形式获取View显示的内容(类似于截图)
public static Bitmap loadBitmapFromView(View view) {
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
private int count = 0;
//tvInImage的x方向和y方向移动量
private float mDx, mDy;
//移动
private class SimpleGestureListenerImpl extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//向右移动时,distanceX为负;向左移动时,distanceX为正
//向下移动时,distanceY为负;向上移动时,distanceY为正
count++;
mDx -= distanceX;
mDy -= distanceY;
//边界检查
mDx = calPosition(imagePositionX - tvInImage.getX(), imagePositionX + imageWidth - (tvInImage.getX() + tvInImage.getWidth()), mDx);
mDy = calPosition(imagePositionY - tvInImage.getY(), imagePositionY + imageHeight - (tvInImage.getY() + tvInImage.getHeight()), mDy);
//控制刷新频率
if (count % 5 == 0) {
tvInImage.setX(tvInImage.getX() + mDx);
tvInImage.setY(tvInImage.getY() + mDy);
}
return true;
}
}
//计算正确的显示位置(不能超出边界)
private float calPosition(float min, float max, float current) {
if (current < min) {
return min;
}
if (current > max) {
return max;
}
return current;
}
//获取压缩后的bitmap
private Bitmap getScaledBitmap(int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), resId, opt);
opt.inSampleSize = Utility.calculateInSampleSize(opt, 600, 800);
opt.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(getResources(), resId, opt);
}
}
一个工具类:
public class Utility {
//计算 inSampleSize 值,压缩图片
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
布局文件:
<?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:padding="10dp">
<RelativeLayout
android:id="@+id/writeText_img_rl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<ImageView
android:id="@+id/writeText_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxHeight="360dp"
android:adjustViewBounds="true"
android:contentDescription="@null"/>
<TextView
android:id="@+id/writeText_image_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
android:layout_centerInParent="true"
android:background="#79652a"
android:clickable="true"
android:padding="4dp"
android:textColor="@android:color/white"
android:textSize="15sp" />
</RelativeLayout>
<EditText
android:id="@+id/writeText_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="添加备注" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="confirm"
android:text="生成图片" />
</LinearLayout>
更多关于Android相关内容感兴趣的读者可查看本站专题:《Android图形与图像处理技巧总结》、《Android开发入门与进阶教程》、《Android调试技巧与常见问题解决方法汇总》、《Android基本组件用法总结》、《Android视图View技巧总结》、《Android布局layout技巧总结》及《Android控件用法总结》
希望本文所述对大家Android程序设计有所帮助。
# Android
# 拖动
# 改变位置
# 图片
# 叠加
# 文字
# Android编程实现图片背景渐变切换与图层叠加效果
# Android实现图片叠加效果的两种方法
# android中TabHost的图标(48×48)和文字叠加解决方法
# Android实现图片叠加功能
# 写在
# 自己的
# 换行
# 进阶
# 是因为
# 也会
# 相关内容
# 不多
# 中有
# 感兴趣
# 给大家
# 弹出
# 可以使用
# 很明显
# 更多关于
# 类似于
# 解决方法
# 贴上
# 所述
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
php增删改查怎么学_零基础入门php数据库操作必知基础【教程】
西安市网站制作公司,哪个相亲网站比较好?西安比较好的相亲网站?
Laravel怎么为数据库表字段添加索引以优化查询
Python自动化办公教程_ExcelWordPDF批量处理案例
网站图片在线制作软件,怎么在图片上做链接?
,交易猫的商品怎么发布到网站上去?
android nfc常用标签读取总结
悟空识字怎么关闭自动续费_悟空识字取消会员自动扣费步骤
canvas 画布在主流浏览器中的尺寸限制详细介绍
JavaScript中的标签模板是什么_它如何扩展字符串功能
Laravel如何发送系统通知_Laravel Notifications实现多渠道消息通知
Win11怎么设置默认图片查看器_Windows11照片应用关联设置
Thinkphp 中 distinct 的用法解析
Laravel如何使用Seeder填充数据_Laravel模型工厂Factory批量生成测试数据【方法】
html5源代码发行怎么设置权限_访问权限控制方法与实践【指南】
EditPlus中的正则表达式实战(5)
如何基于PHP生成高效IDC网络公司建站源码?
JavaScript如何实现继承_有哪些常用方法
Laravel怎么调用外部API_Laravel Http Client客户端使用
网站制作报价单模板图片,小松挖机官方网站报价?
Laravel如何发送邮件_Laravel Mailables构建与发送邮件的简明教程
jQuery中的100个技巧汇总
怎么制作一个起泡网,水泡粪全漏粪育肥舍冬季氨气超过25ppm,可以有哪些措施降低舍内氨气水平?
如何用搬瓦工VPS快速搭建个人网站?
微信小程序 canvas开发实例及注意事项
Laravel如何编写单元测试和功能测试?(PHPUnit示例)
java中使用zxing批量生成二维码立牌
Laravel怎么创建自己的包(Package)_Laravel扩展包开发入门到发布
phpredis提高消息队列的实时性方法(推荐)
ChatGPT 4.0官网入口地址 ChatGPT在线体验官网
网站广告牌制作方法,街上的广告牌,横幅,用PS还是其他软件做的?
html5如何实现懒加载图片_ intersectionobserver api用法【教程】
Laravel如何将应用部署到生产服务器_Laravel生产环境部署流程
哪家制作企业网站好,开办像阿里巴巴那样的网络公司和网站要怎么做?
C++用Dijkstra(迪杰斯特拉)算法求最短路径
Laravel如何使用Socialite实现第三方登录?(微信/GitHub示例)
Angular 表单中正确绑定输入值以确保提交与验证正常工作
Laravel怎么做缓存_Laravel Cache系统提升应用速度的策略与技巧
标题:Vue + Vuex 项目中正确使用 JWT 进行身份认证的实践指南
如何挑选高效建站主机与优质域名?
Laravel Blade模板引擎语法_Laravel Blade布局继承用法
Laravel如何实现登录错误次数限制_Laravel自带LoginThrottles限流配置【方法】
JavaScript Ajax实现异步通信
Laravel如何使用withoutEvents方法临时禁用模型事件
如何构建满足综合性能需求的优质建站方案?
佛山企业网站制作公司有哪些,沟通100网上服务官网?
Laravel如何处理CORS跨域问题_Laravel项目CORS配置与解决方案
iOS验证手机号的正则表达式
Laravel Docker环境搭建教程_Laravel Sail使用指南
开心动漫网站制作软件下载,十分开心动画为何停播?

