一张图片不仅价值一千个单词,而且比单调的文字更酷。在本文中,我们将学习如何从TextView中创建贴纸。如果要创建要在其中添加一些文本叠加功能的应用程序,例如图像编辑应用程序,这将很有帮助。另外,我们可以在Hike App中看到一个很好的实现。
它是如何工作的?
我们在这里使用的主要概念是将TextView转换为Bitmap并将该位图保存在手机存储中。现在,我们可以在该TextView中进行“ n”次操作,就像我们可以更改TextView的颜色,字体,大小,样式,外观等一样。我们在TextView中所做的任何更改都将反映在形成的贴纸(位图)中。牢记这个想法,让我们开始创建App。注意,我们将使用Java语言实现该项目。
分步实施
步骤1:创建一个新项目
要在Android Studio中创建新项目,请参阅如何在Android Studio中创建/启动新项目。请注意,选择Java作为编程语言。
步骤2:将依赖项添加到build.gradle文件
我们将使用一个库在运行时拾取颜色并相应地更改TextView的颜色。因此,在build.gradle文件中添加此依赖项。
implementation ‘petrov.kristiyan:colorpicker-library:1.1.10’
步骤3:添加字体文件
为了使我们的标签更加美观,我们将在TextView中添加一些字体。为此,我们将需要.ttf或.otf格式的字体文件。您可以从互联网上下载任何喜欢的字体文件。现在,一旦下载了文件,请转到应用程序> res>右键单击>新建>文件夹>字体文件夹,然后在android studio中创建该字体的新Android资源文件夹,然后将字体文件放置在该文件夹中。
例如,我们将使用六个字体文件,您可以从此处下载这些文件。
步骤4:使用activity_main.xml文件
转到activity_main.xml文件,并参考以下代码。以下是activity_main.xml文件的代码。
XML
Java
import android.app.ProgressDialog;
import android.content.ContentValues;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.res.ResourcesCompat;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import petrov.kristiyan.colorpicker.ColorPicker;
public class MainActivity extends AppCompatActivity {
TextView textSticker;
EditText editTextSticker;
ImageButton fontchange;
ImageButton colorPickerText;
Button createSticker;
// this will work as a counter to
// change the font of TextView
int i = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textSticker = (TextView) findViewById(R.id.stickerTextview);
editTextSticker = (EditText) findViewById(R.id.stickerEditText);
colorPickerText = (ImageButton) findViewById(R.id.changeColor);
fontchange = (ImageButton) findViewById(R.id.changeFont);
createSticker = (Button) findViewById(R.id.convert);
createSticker.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
executeSticker();
} catch (IOException e) {
e.printStackTrace();
}
}
});
// Here we have added a TextWatcher. The onTextChanged() method will change
// the text in TextVie as we type, in the EditText. This makes app more interactive.
editTextSticker.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
textSticker.setText(charSequence);
}
@Override
public void afterTextChanged(Editable editable) {
}
});
// Here we have implemented a small logic which changes the font of the TextView
// Whenever we click this button. The counter increments by one and reset to zero
// when it reaches value 6.
fontchange.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
switch (i) {
case 0:
i = 1;
// This is a very important method of this example.
// The setTypeFace() method sets the font of the TextView at runtime.
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.summer));
break;
case 1:
i = 2;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.angel));
break;
case 2:
i = 3;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.cute));
break;
case 3:
i = 4;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.mandala));
break;
case 4:
i = 5;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.painter));
break;
case 5:
i = 6;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.newfont));
break;
case 6:
i = 0;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.orange));
break;
}
}
});
colorPickerText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// create an instance of ColorPicker and invoke the ColorPicker dialog onClick.
final ColorPicker colorPicker = new ColorPicker(MainActivity.this);
colorPicker.setOnFastChooseColorListener(new ColorPicker.OnFastChooseColorListener() {
@Override
public void setOnFastChooseColorListener(int position, int color) {
// get the integer value of color selected from the dialog box and
// the color of the TextView.
textSticker.setTextColor(color);
}
@Override
public void onCancel() {
colorPicker.dismissDialog();
}
})
// set the number of color columns
// you want to show in dialog.
.setColumns(5)
// set a default color selected in the dialog
.setDefaultColorButton(Color.parseColor("#000000"))
.show();
}
});
}
// This method creates a Bitmap from the TextView
// and saves that into the storage
private void executeSticker() throws IOException {
// Create an OutputStream to write the file in storage
OutputStream imageOutStream;
// Although the ProgressDialog is not necessary but there may be cases when
// it might takes 2-3seconds in creating the bitmap.(This happens only when there is a
// large chunk of cache and also when app is running multiple threads)
final ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setMessage("Please wait..");
progressDialog.show();
// All the three methods are discussed later in this article.
// destroyDrawingCache(),buildDrawingCache(),getDrawingCache().
textSticker.destroyDrawingCache();
textSticker.buildDrawingCache();
Bitmap textStickerBitmap = textSticker.getDrawingCache();
// From Android 10 onwards using the former method gives error, because
// there is a security/privacy update in Android Q which doesn't allow
// access to third party files.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// In order to create a new image file in
// storage we do the following steps.
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, "gfg.png");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
imageOutStream = getContentResolver().openOutputStream(uri);
// this method writes the file in storage. And finally
// our sticker has been created and
// successfully saved in our storage
textStickerBitmap.compress(Bitmap.CompressFormat.PNG, 100, imageOutStream);
// close the output stream after use.
imageOutStream.close();
progressDialog.dismiss();
// Now, incase you want to use that bitmap(sticker)
// at the very moment it is created
// we do the following steps.
// Open a Inputstream to get the data from file
final InputStream imageStream;
try {
// use the same uri which we previously used
// in writing the file, as it contains
// the path to that file.
imageStream = getContentResolver().openInputStream(uri);
final Bitmap selectedImage = BitmapFactory.decodeStream(imageStream);
// create a drawable from bitmap.
Drawable drawable = new BitmapDrawable(getResources(), selectedImage);
// You can do anything with this drawable.
// This drawable contains sticker png.
} catch (FileNotFoundException e) {
Toast.makeText(MainActivity.this, "File not found!!", Toast.LENGTH_SHORT).show();
}
// The else condition is executed if the device
// Android version is less than Android 10
} else {
String imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
File image = new File(imagesDir, "gfg.jpg");
imageOutStream = new FileOutputStream(image);
textStickerBitmap.compress(Bitmap.CompressFormat.PNG, 100, imageOutStream);
imageOutStream.close();
final Uri imageUri = Uri.fromFile(image);
final InputStream imageStream;
try {
imageStream = getContentResolver().openInputStream(imageUri);
final Bitmap selectedImage = BitmapFactory.decodeStream(imageStream);
Drawable drawable = new BitmapDrawable(getResources(), selectedImage);
// You can do anything with this drawable.
// This drawable contains sticker png.
} catch (FileNotFoundException e) {
Toast.makeText(MainActivity.this, "File not found!!", Toast.LENGTH_SHORT).show();
}
}
// Finally, print a success message.
Toast.makeText(this, "Sticker created successfully!!", Toast.LENGTH_SHORT).show();
}
}
步骤5:使用MainActivity。 Java文件
转到MainActivity。 Java文件并参考以下代码。下面是MainActivity的代码。 Java文件。在代码内部添加了注释,以更详细地了解代码。
Java
import android.app.ProgressDialog;
import android.content.ContentValues;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.res.ResourcesCompat;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import petrov.kristiyan.colorpicker.ColorPicker;
public class MainActivity extends AppCompatActivity {
TextView textSticker;
EditText editTextSticker;
ImageButton fontchange;
ImageButton colorPickerText;
Button createSticker;
// this will work as a counter to
// change the font of TextView
int i = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textSticker = (TextView) findViewById(R.id.stickerTextview);
editTextSticker = (EditText) findViewById(R.id.stickerEditText);
colorPickerText = (ImageButton) findViewById(R.id.changeColor);
fontchange = (ImageButton) findViewById(R.id.changeFont);
createSticker = (Button) findViewById(R.id.convert);
createSticker.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
executeSticker();
} catch (IOException e) {
e.printStackTrace();
}
}
});
// Here we have added a TextWatcher. The onTextChanged() method will change
// the text in TextVie as we type, in the EditText. This makes app more interactive.
editTextSticker.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
textSticker.setText(charSequence);
}
@Override
public void afterTextChanged(Editable editable) {
}
});
// Here we have implemented a small logic which changes the font of the TextView
// Whenever we click this button. The counter increments by one and reset to zero
// when it reaches value 6.
fontchange.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
switch (i) {
case 0:
i = 1;
// This is a very important method of this example.
// The setTypeFace() method sets the font of the TextView at runtime.
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.summer));
break;
case 1:
i = 2;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.angel));
break;
case 2:
i = 3;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.cute));
break;
case 3:
i = 4;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.mandala));
break;
case 4:
i = 5;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.painter));
break;
case 5:
i = 6;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.newfont));
break;
case 6:
i = 0;
textSticker.setTypeface(ResourcesCompat.getFont(MainActivity.this, R.font.orange));
break;
}
}
});
colorPickerText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// create an instance of ColorPicker and invoke the ColorPicker dialog onClick.
final ColorPicker colorPicker = new ColorPicker(MainActivity.this);
colorPicker.setOnFastChooseColorListener(new ColorPicker.OnFastChooseColorListener() {
@Override
public void setOnFastChooseColorListener(int position, int color) {
// get the integer value of color selected from the dialog box and
// the color of the TextView.
textSticker.setTextColor(color);
}
@Override
public void onCancel() {
colorPicker.dismissDialog();
}
})
// set the number of color columns
// you want to show in dialog.
.setColumns(5)
// set a default color selected in the dialog
.setDefaultColorButton(Color.parseColor("#000000"))
.show();
}
});
}
// This method creates a Bitmap from the TextView
// and saves that into the storage
private void executeSticker() throws IOException {
// Create an OutputStream to write the file in storage
OutputStream imageOutStream;
// Although the ProgressDialog is not necessary but there may be cases when
// it might takes 2-3seconds in creating the bitmap.(This happens only when there is a
// large chunk of cache and also when app is running multiple threads)
final ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setMessage("Please wait..");
progressDialog.show();
// All the three methods are discussed later in this article.
// destroyDrawingCache(),buildDrawingCache(),getDrawingCache().
textSticker.destroyDrawingCache();
textSticker.buildDrawingCache();
Bitmap textStickerBitmap = textSticker.getDrawingCache();
// From Android 10 onwards using the former method gives error, because
// there is a security/privacy update in Android Q which doesn't allow
// access to third party files.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// In order to create a new image file in
// storage we do the following steps.
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, "gfg.png");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
imageOutStream = getContentResolver().openOutputStream(uri);
// this method writes the file in storage. And finally
// our sticker has been created and
// successfully saved in our storage
textStickerBitmap.compress(Bitmap.CompressFormat.PNG, 100, imageOutStream);
// close the output stream after use.
imageOutStream.close();
progressDialog.dismiss();
// Now, incase you want to use that bitmap(sticker)
// at the very moment it is created
// we do the following steps.
// Open a Inputstream to get the data from file
final InputStream imageStream;
try {
// use the same uri which we previously used
// in writing the file, as it contains
// the path to that file.
imageStream = getContentResolver().openInputStream(uri);
final Bitmap selectedImage = BitmapFactory.decodeStream(imageStream);
// create a drawable from bitmap.
Drawable drawable = new BitmapDrawable(getResources(), selectedImage);
// You can do anything with this drawable.
// This drawable contains sticker png.
} catch (FileNotFoundException e) {
Toast.makeText(MainActivity.this, "File not found!!", Toast.LENGTH_SHORT).show();
}
// The else condition is executed if the device
// Android version is less than Android 10
} else {
String imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
File image = new File(imagesDir, "gfg.jpg");
imageOutStream = new FileOutputStream(image);
textStickerBitmap.compress(Bitmap.CompressFormat.PNG, 100, imageOutStream);
imageOutStream.close();
final Uri imageUri = Uri.fromFile(image);
final InputStream imageStream;
try {
imageStream = getContentResolver().openInputStream(imageUri);
final Bitmap selectedImage = BitmapFactory.decodeStream(imageStream);
Drawable drawable = new BitmapDrawable(getResources(), selectedImage);
// You can do anything with this drawable.
// This drawable contains sticker png.
} catch (FileNotFoundException e) {
Toast.makeText(MainActivity.this, "File not found!!", Toast.LENGTH_SHORT).show();
}
}
// Finally, print a success message.
Toast.makeText(this, "Sticker created successfully!!", Toast.LENGTH_SHORT).show();
}
}
输出:在物理设备上运行
Note about auto-scaling in compatibility mode: When auto-scaling is not enabled, this method will create a bitmap of the same size as this view. You should avoid calling this method when hardware acceleration is enabled. If you do not need the drawing cache bitmap, calling this method will increase memory usage and cause the view to be rendered in software once, thus negatively impacting performance.
本示例中使用的重要方法
- buildDrawingCache:如果图形缓存无效,则强制构建图形缓存。
- destroyDrawingCache:释放图形缓存使用的资源。如果您手动调用buildDrawingCache()而不调用setDrawingCacheEnabled(true),则此后应使用此方法清除缓存。
- getDrawingCache:返回在其中缓存此视图图形的位图。禁用缓存时,返回的位图为null。如果启用了缓存并且缓存尚未就绪,则此方法将创建它。此函数的重载函数带有布尔参数,该布尔参数指示当应用程序处于兼容模式时,是否应基于屏幕的当前密度缩放生成的位图。零参数方法的工作原理与getDrawingCache(false)相同。
- setDrawingCacheEnabled:启用或禁用图形缓存。启用图形缓存后,对getDrawingCache()或buildDrawingCache()的下一次调用将在位图中绘制查看器。
未来范围
- 您可以在运行时添加增加或减少TextView大小的功能。
- 您可以将画布添加到您的应用程序,这将允许拖动,拉伸,旋转TextView,这将更具交互性。
- 您也可以尝试在聊天应用程序中实现此功能,在该应用程序中,用户可以根据键入的文本生成标签。
- 尝试使用其他语言输入并创建标签。
Notes:
1. We don’t require read write permissions here because the stickers or the bitmaps which we are saving in phone storage are owned by our own app.
2. From Android 10, method for accessing files from device storage has been changed. If you use earlier methods you might get absurd results.
3. If you call buildDrawingCache() manually without calling setDrawingCacheEnabled(true), you should cleanup the cache by calling destroyDrawingCache() afterwards.