效果图
准备
我们先准备几张表情用ps把文字去掉
由于要用到recyclerview 先在app\build.gradle添加如下代码
implementation 'com.android.support:recyclerview-v7:26.1.0'
在AndroidManifest添加
读写sd卡权限
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
这句代码加不加都可以,防止输入法挡住编辑框 不加的话看起来可能会有怪怪的感觉
android:windowSoftInputMode="adjustPan"
把刚刚我们准备好的图片导入进来
布局
新建布局main.xml(这是我们的主界面,activity_main.xml现在是多余的 不过我们先留着)
main.xml
<?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"> <com.wsbq.liziguo.hua android:id="@+id/hua" android:layout_width="280dp" android:layout_height="280dp" android:layout_gravity="center" > </com.wsbq.liziguo.hua> <TextView android:text="点击屏幕移动文字" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/xiao" android:layout_weight="1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="A-" /> <Button android:id="@+id/bt" android:layout_weight="1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="保存" /> <Button android:id="@+id/da" android:layout_weight="1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="A+" /> </LinearLayout> <EditText android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="请输入" /> <android.support.v7.widget.RecyclerView android:id="@+id/recy" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
大概长这个比样
然后改一下MainActivity onCreate方法里的
setContentView(R.layout.main);//这里改一下,使用的不是activity_main布局
然后写我们RecyclerView Item里的布局
这里我们就不新建布局文件了,我们直接使用activity_main.xml
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:context="com.wsbq.liziguo.MainActivity"> <ImageView android:id="@+id/img" android:layout_width="180dp" android:layout_height="180dp" /> </LinearLayout>
里面就一个ImageView
好了,下面开始敲代码吧
新建类hua继承View,用来话表情和文字
hua.java
package com.wsbq.liziguo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; /** * Created by Liziguo on 2018/5/31. */ public class hua extends View {//这个view叫做“画”用来画表情和文字 private int w, h;//view宽高 public float x, y;//控制文本位置 public Paint paint;//画笔 public Bitmap img = null;//表情图片 public hua(Context context, AttributeSet attrs) { super(context, attrs); paint = new Paint(); paint.setColor(Color.BLACK); paint.setTextSize(70);//初始文字大小为70 // setBackgroundColor(Color.WHITE);//一开始这个类的继承ViewGroup的 如果不添加这句图片画不出来 setOnTouchListener(new OnTouchListener() {//添加触摸事件 @Override public boolean onTouch(View v, MotionEvent e) {//调节文本位置 x = e.getX() - paint.getTextSize(); y = e.getY(); invalidate();//刷新画布 return true; } }); } @Override protected void onDraw(Canvas canvas) {//画图像 super.onDraw(canvas); if (img != null) canvas.drawBitmap(img, null, new RectF(0, 0, w, h), paint);//画表情图片,平铺整个view if (MainActivity.main.text != null) { String[] s = MainActivity.main.text.getText().toString().split("\n");//检测换行符,切割字符串 for (int i = 0; i < s.length; i++) { canvas.drawText(s[i], x, y + i * paint.getTextSize(), paint);//这样就能显示多行文字了 } } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); //将宽高保存起来 this.w = w; this.h = h; y = h * 0.75f;//文字初始y轴坐标 } }
画布做好了 接下来编写MainActivity.java里的代码
MainActivity.java
package com.wsbq.liziguo; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Environment; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.ImageView; import android.widget.Toast; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { public static MainActivity main;//当一个全局变量使用 public hua hua;//这个view用来画图片和文字 public EditText text;//用户输入的文字 private RecyclerView recy;//滑动控件 private List<Integer> list; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); main = this; setContentView(R.layout.main);//这里改一下,使用的不是activity_main布局 hua = findViewById(R.id.hua); text = findViewById(R.id.text); recy = findViewById(R.id.recy); //设为横向滚动 recy.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); //初始化表情图片,我们把他们的id存起来 list = new ArrayList<Integer>(); list.add(R.mipmap.jgz); list.add(R.mipmap.jh); list.add(R.mipmap.zxy); list.add(R.mipmap.jh2); list.add(R.mipmap.jh3); list.add(R.mipmap.jh4); list.add(R.mipmap.jgz2); list.add(R.mipmap.fls); list.add(R.mipmap.fls2); list.add(R.mipmap.ku); list.add(R.mipmap.zxy2); hua.img = BitmapFactory.decodeResource(getResources(), list.get(0)); recy.setAdapter(new adapter(list));//设置适配器 findViewById(R.id.xiao).setOnClickListener(new View.OnClickListener() {//添加Button事件 字体缩小 @Override public void onClick(View v) { float f = hua.paint.getTextSize(); if (f > 10) { f--; } hua.paint.setTextSize(f); hua.invalidate(); } }); findViewById(R.id.da).setOnClickListener(new View.OnClickListener() {//添加Button事件 字体放大 @Override public void onClick(View v) { float f = hua.paint.getTextSize(); f++; hua.paint.setTextSize(f); hua.invalidate(); } }); findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {//添加Button事件 保存图片 @Override public void onClick(View v) { hua.invalidate();//刷新画布 hua.buildDrawingCache();//截图,区域是整个hua Bitmap img = hua.getDrawingCache();//获取调用buildDrawingCache()时截取的图片 String name = "表情" + System.currentTimeMillis() + ".jpg"; //文件的路径为 sd卡/表情包/ File f = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/表情包/" + name); //判断文件父目录是否存在,不存在则创建 if (!f.getParentFile().exists()) { f.getParentFile().mkdirs(); } try { BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(f)); img.compress(Bitmap.CompressFormat.JPEG, 50, out);//参数(格式,品质,输出到流) out.flush(); out.close(); Toast.makeText(getApplicationContext(), "图片已保存到" + f.getParent(), Toast.LENGTH_SHORT).show(); //下面这一段代码非常重要 不然图库/QQ/微信都找不到你的图片(重启手机才能看到) //我们要发送一个广播通知图库刷新你的相册 //当然手机sd卡那么大全部扫描一遍的话很影响用户体验,所以这里我们只扫描刚刚输出的图片 Intent in = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(f));//发送广播通知图库刷新amssf sendBroadcast(in);//发送广播 } catch (IOException e) { e.printStackTrace(); } } }); text.setSelection(text.getText().toString().length()); verifyStoragePermissions(this);//动态申请权限 } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //这一段代码是复制过来的 动态申请sd卡权限 //设备系统是 Android 6.0 (API 23) 或更高版本,并且应用的 targetSdkVersion 是 23 或更高版本 //则针对在 AndroidManifest.xml 中声明的危险权限,在运行时还需要动态请求用户授权。例如读写sd卡 //这一段也是整个程序写好了才加上来的,因为这个软件在我手机和虚拟机上完美运行 到了别人手机就保存不了了 //说好的Android 6.0呢?我手机Android 7.1.2都不用动态权限。。。 private static final int REQUEST_EXTERNAL_STORAGE = 1; private static String[] PERMISSIONS_STORAGE = { "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE"}; public static void verifyStoragePermissions(Activity activity) { try { //检测是否有写的权限 int permission = ActivityCompat.checkSelfPermission(activity, "android.permission.WRITE_EXTERNAL_STORAGE"); if (permission != PackageManager.PERMISSION_GRANTED) { // 没有写的权限,去申请写的权限,会弹出对话框 ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE); } } catch (Exception e) { e.printStackTrace(); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } class adapter extends RecyclerView.Adapter<adapter.viewh> {//RecyclerView适配器 private List<Integer> list; public adapter(List l) { list = l; } @Override public viewh onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_main, parent, false); viewh h = new viewh(v); return h; } @Override public void onBindViewHolder(final viewh holder, int position) { final int i = list.get(position); holder.img.setImageResource(i); holder.itemView.setOnClickListener(new View.OnClickListener() {//更换图片点击事件。这样做会影响性能,先这样写吧 @Override public void onClick(View v) { MainActivity.main.hua.img = BitmapFactory.decodeResource(MainActivity.main.getResources(), i); MainActivity.main.hua.invalidate(); } }); } @Override public int getItemCount() {//这里要改一下 不然显示不出来 return list.size(); } static class viewh extends RecyclerView.ViewHolder { ImageView img; View itemView; public viewh(View itemView) { super(itemView); img = itemView.findViewById(R.id.img); this.itemView = itemView; } } }
大功告成
总结:做这个项目的时候碰了两次壁,一是申请动态权限不然有的手机可能保存不了,二是广播通知图库更新相册不然QQ微信找不到这张图片
下载地址:https://download.csdn.net/download/u010756046/10454405 (源码+素材)