├── README.md ├── Screenshot_2016-06-20.png ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── rosen │ │ └── sticktextview │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── rosen │ │ │ └── sticktextview │ │ │ ├── MainActivity.java │ │ │ ├── utils │ │ │ ├── CommonUtils.java │ │ │ └── PrintUtils.java │ │ │ └── view │ │ │ ├── StickerTextView.java │ │ │ └── TextViewItem.java │ └── res │ │ ├── drawable │ │ ├── bg.png │ │ └── bg_test.png │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── content_main.xml │ │ └── text_layout.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── edit_wz_bg_btn1.png │ │ ├── edit_wz_bg_btn2.png │ │ ├── edit_wz_bg_btn3.png │ │ ├── ic_launcher.png │ │ ├── text_button_02.png │ │ └── txt_button_0.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── cn │ └── rosen │ └── sticktextview │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── local.properties └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | ### StickTextView 2 | #### Android图片文字贴纸功能 3 | #### 感谢http://blog.csdn.net/rosenluo/article/details/51099138 4 | > 可以设置自定义背景,自定义设置文字的位置,可以限制文字字数,可以设置单行多行模式 5 | 6 | > ![效果截图](https://github.com/hanbaokun/StickTextView/blob/master/Screenshot_2016-06-20.png) 7 | 8 | -------------------------------------------------------------------------------- /Screenshot_2016-06-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanbaokun/StickTextView/e0dab12f8806208d5c016f24cafa8a0d679db035/Screenshot_2016-06-20.png -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion '23.0.1' 6 | 7 | defaultConfig { 8 | applicationId "cn.rosen.sizeadjusttextstickview" 9 | minSdkVersion 15 10 | targetSdkVersion 18 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.0.1' 26 | compile 'com.android.support:design:23.0.1' 27 | } 28 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\D\android-sdks-64/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/rosen/sticktextview/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package cn.rosen.sticktextview; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/cn/rosen/sticktextview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.rosen.sticktextview; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.support.design.widget.FloatingActionButton; 8 | import android.support.design.widget.Snackbar; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.support.v7.widget.Toolbar; 11 | import android.text.Editable; 12 | import android.text.TextWatcher; 13 | import android.util.TypedValue; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.widget.EditText; 17 | import android.widget.LinearLayout; 18 | import android.widget.RelativeLayout; 19 | import android.widget.TextView; 20 | import android.widget.Toast; 21 | 22 | import cn.rosen.sticktextview.view.StickerTextView; 23 | import cn.rosen.sticktextview.view.TextViewItem; 24 | import cn.rosen.sticktextview.utils.CommonUtils; 25 | import cn.rosen.sticktextview.utils.PrintUtils; 26 | 27 | public class MainActivity extends AppCompatActivity implements StickerTextView.OnStickerTextTouchListener { 28 | 29 | RelativeLayout contentLayout; 30 | View editLayout; 31 | View doButton; 32 | private EditText editText; 33 | private StickerTextView stickerTextView; 34 | private TextViewItem textViewItem; 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_main); 40 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 41 | setSupportActionBar(toolbar); 42 | 43 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 44 | fab.setOnClickListener(new View.OnClickListener() { 45 | @Override 46 | public void onClick(View view) { 47 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 48 | .setAction("Action", null).show(); 49 | } 50 | }); 51 | contentLayout = (RelativeLayout) findViewById(R.id.content_layout); 52 | editLayout = findViewById(R.id.do_reset_text_layout); 53 | doButton = findViewById(R.id.do_reset_finish); 54 | editText = (EditText) findViewById(R.id.edit_text); 55 | addStikerTextView(); 56 | doButton.setOnClickListener(new View.OnClickListener() { 57 | @Override 58 | public void onClick(View v) { 59 | editLayout.setVisibility(View.GONE); 60 | CommonUtils.hideInputMethod(MainActivity.this, editText); 61 | } 62 | }); 63 | editText.addTextChangedListener(new TextWatcher() { 64 | @Override 65 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 66 | 67 | } 68 | 69 | @Override 70 | public void onTextChanged(CharSequence s, int start, int before, int count) { 71 | 72 | } 73 | 74 | @Override 75 | public void afterTextChanged(Editable s) { 76 | int nums = textViewItem.getNums(); 77 | if(s.length()<=nums){ 78 | textViewItem.getTextView().setText(s.toString(),TextView.BufferType.NORMAL); 79 | stickerTextView.updateTextDraw(textViewItem.getTextView(), textViewItem.isSingleLine()); 80 | }else { 81 | Toast.makeText(MainActivity.this, "字数限制"+nums, Toast.LENGTH_SHORT).show(); 82 | } 83 | } 84 | }); 85 | } 86 | 87 | private void addStikerTextView() { 88 | stickerTextView = new StickerTextView(getBaseContext()); 89 | stickerTextView.setOnStickerTextTouchListener(this); 90 | //设置显示位置 91 | RelativeLayout.LayoutParams rl = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); 92 | rl.addRule(RelativeLayout.CENTER_IN_PARENT); 93 | //设置背景 94 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg_test); 95 | stickerTextView.setBackgroundBitmap(bitmap); 96 | //添加文字元素 97 | TextView s1 = new TextView(getBaseContext()); 98 | s1.setText("静", TextView.BufferType.NORMAL); 99 | s1.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 100 | s1.setBackgroundColor(Color.parseColor("#00000000")); 101 | s1.setTextColor(Color.BLACK); 102 | s1.setTextSize(getTextSize(10)); 103 | 104 | TextView s2 = new TextView(getBaseContext()); 105 | s2.setText("默", TextView.BufferType.NORMAL); 106 | s2.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 107 | s2.setBackgroundColor(Color.parseColor("#00000000")); 108 | s2.setTextColor(Color.BLACK); 109 | s2.setTextSize(getTextSize(10)); 110 | 111 | TextView s3 = new TextView(getBaseContext()); 112 | s3.setText("时", TextView.BufferType.NORMAL); 113 | s3.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 114 | s3.setBackgroundColor(Color.parseColor("#00000000")); 115 | s3.setTextColor(Color.BLACK); 116 | s3.setTextSize(getTextSize(10)); 117 | 118 | TextView s4 = new TextView(getBaseContext()); 119 | s4.setText("光", TextView.BufferType.NORMAL); 120 | s4.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 121 | s4.setBackgroundColor(Color.parseColor("#00000000")); 122 | s4.setTextColor(Color.BLACK); 123 | s4.setTextSize(getTextSize(10)); 124 | 125 | TextView s5 = new TextView(getBaseContext()); 126 | s5.setText("我们在一起/\n不需要孤独/\n独处时的静默时光/", TextView.BufferType.NORMAL); 127 | s5.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 128 | s5.setBackgroundColor(Color.parseColor("#00000000")); 129 | s5.setTextColor(Color.WHITE); 130 | s5.setLines(5); 131 | s5.setLineSpacing(1.0f,2.0f); 132 | s5.setTextSize(getTextSize(5)); 133 | 134 | 135 | stickerTextView.addTextDraw(s1, 10, 10, 50, 50,true,1); 136 | stickerTextView.addTextDraw(s2, 60, 10, 100, 50,true,1); 137 | stickerTextView.addTextDraw(s3, 110, 10, 150, 50,true,1); 138 | stickerTextView.addTextDraw(s4, 160, 10, 200, 50,true,1); 139 | stickerTextView.addTextDraw(s5, 15, 60, 180, 200,false,30); 140 | 141 | contentLayout.addView(stickerTextView, rl); 142 | } 143 | 144 | private float getTextSize(float size) { 145 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, size, getResources().getDisplayMetrics()); 146 | } 147 | 148 | @Override 149 | public void onTextCopy(StickerTextView stickerView) { 150 | PrintUtils.println("点击了复制"); 151 | } 152 | 153 | @Override 154 | public void onTextDelete(StickerTextView stickerView) { 155 | PrintUtils.println("点击了删除"); 156 | contentLayout.removeView(stickerView); 157 | } 158 | 159 | @Override 160 | public void onTextMoveToHead(StickerTextView stickerView) { 161 | PrintUtils.println("触摸到控件时会调用该方法"); 162 | } 163 | 164 | @Override 165 | public void onTextClickCurrentText(TextViewItem textViewItem) { 166 | this.textViewItem = textViewItem; 167 | PrintUtils.println("单击调用"); 168 | editLayout.setVisibility(View.VISIBLE); 169 | editText.setText(textViewItem.getTextView().getText()); 170 | CommonUtils.showInputMethod(MainActivity.this, editText); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /app/src/main/java/cn/rosen/sticktextview/utils/CommonUtils.java: -------------------------------------------------------------------------------- 1 | package cn.rosen.sticktextview.utils; 2 | 3 | import android.app.Activity; 4 | import android.app.ActivityManager; 5 | import android.app.PendingIntent; 6 | import android.app.PendingIntent.CanceledException; 7 | import android.content.ComponentName; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.content.pm.ApplicationInfo; 11 | import android.content.pm.PackageInfo; 12 | import android.content.pm.PackageManager; 13 | import android.content.pm.PackageManager.NameNotFoundException; 14 | import android.content.pm.ResolveInfo; 15 | import android.database.Cursor; 16 | import android.graphics.Bitmap; 17 | import android.graphics.BitmapFactory; 18 | import android.graphics.Paint; 19 | import android.graphics.drawable.Drawable; 20 | import android.location.LocationManager; 21 | import android.media.MediaScannerConnection; 22 | import android.net.ConnectivityManager; 23 | import android.net.NetworkInfo; 24 | import android.net.Uri; 25 | import android.os.Bundle; 26 | import android.os.Environment; 27 | import android.os.StatFs; 28 | import android.telephony.TelephonyManager; 29 | import android.text.ClipboardManager; 30 | import android.text.TextUtils; 31 | import android.util.DisplayMetrics; 32 | import android.util.Log; 33 | import android.view.Display; 34 | import android.view.View; 35 | import android.view.Window; 36 | import android.view.WindowManager; 37 | import android.view.animation.TranslateAnimation; 38 | import android.view.inputmethod.InputMethodManager; 39 | import android.webkit.WebSettings; 40 | import android.webkit.WebView; 41 | import android.widget.EditText; 42 | 43 | import java.io.InputStream; 44 | import java.io.UnsupportedEncodingException; 45 | import java.net.URLDecoder; 46 | import java.net.URLEncoder; 47 | import java.text.Collator; 48 | import java.util.Collections; 49 | import java.util.Comparator; 50 | import java.util.List; 51 | import java.util.Locale; 52 | import java.util.Map; 53 | import java.util.UUID; 54 | 55 | public class CommonUtils{ 56 | 57 | /** 58 | * 获取系统版本 59 | */ 60 | public static int version = android.os.Build.VERSION.SDK_INT; 61 | 62 | /** 63 | * 获取屏幕宽度 64 | * @param act 65 | * @return 66 | */ 67 | public static int getDisplayWidth(Activity act) 68 | { 69 | return act.getWindowManager().getDefaultDisplay().getWidth(); 70 | } 71 | /** 72 | * 获取屏幕高度 73 | * @param act 74 | * @return 75 | */ 76 | public static int getDisplayHeight(Activity act) 77 | { 78 | return act.getWindowManager().getDefaultDisplay().getHeight(); 79 | } 80 | /** 81 | * DisplayMetrics dm = new DisplayMetrics(); 82 | * getWindowManager().getDefaultDisplay().getMetrics(dm); 83 | * int screenWidth = dm.widthPixels; 84 | * int screenHeight = dm.heightPixels; 85 | */ 86 | /** 87 | * 获取屏幕宽度 88 | * @param context 89 | * @return 90 | */ 91 | public static int getScreenWidth(Context context) { 92 | WindowManager manager = (WindowManager) context 93 | .getSystemService(Context.WINDOW_SERVICE); 94 | Display display = manager.getDefaultDisplay(); 95 | return display.getWidth(); 96 | } 97 | /** 98 | * 获取屏幕高度 99 | * @param context 100 | * @return 101 | */ 102 | public static int getScreenHeight(Context context) { 103 | WindowManager manager = (WindowManager) context 104 | .getSystemService(Context.WINDOW_SERVICE); 105 | Display display = manager.getDefaultDisplay(); 106 | return display.getHeight(); 107 | } 108 | /** 109 | * 获取屏幕最小宽度(相对高度来说) 110 | * @param act 111 | * @return 112 | */ 113 | public static int getMinWidth(Activity act) 114 | { 115 | return Math.min(getDisplayWidth(act), getDisplayHeight(act)); 116 | } 117 | 118 | /** 119 | * 获取屏幕像素宽度 120 | * @param context 121 | * @return 122 | */ 123 | public static int getWidthPixels(Context context) 124 | { 125 | DisplayMetrics dm = new DisplayMetrics(); 126 | dm = context.getResources().getDisplayMetrics(); 127 | 128 | return dm.widthPixels; 129 | } 130 | /** 131 | * 获取屏幕像素高度 132 | * @param context 133 | * @return 134 | */ 135 | public static int getHeightPixels(Context context) 136 | { 137 | DisplayMetrics dm = new DisplayMetrics(); 138 | dm = context.getResources().getDisplayMetrics(); 139 | return dm.heightPixels; 140 | } 141 | /** 142 | * 以dp形式获取屏幕像素高度 143 | * @param context 144 | * @return 145 | */ 146 | public static final int getHeightInDp(Context context) { 147 | final float height = getHeightPixels(context); 148 | int heightInDp = pxToDip(context, height); 149 | return heightInDp; 150 | } 151 | /** 152 | * 以dp形式获取屏幕像素宽度 153 | * @param context 154 | * @return 155 | */ 156 | public static final int getWidthInDp(Context context) { 157 | final float width = getWidthPixels(context); 158 | int widthInDp = pxToDip(context, width); 159 | return widthInDp; 160 | } 161 | /** 162 | * 获取屏幕密度 163 | * @param activity 164 | * @return 165 | */ 166 | public static float getScreenDensity(Activity activity){ 167 | DisplayMetrics metric = new DisplayMetrics(); 168 | activity.getWindow().getWindowManager().getDefaultDisplay().getMetrics(metric); 169 | 170 | return metric.density; 171 | } 172 | /** 173 | * 获取根Activity 174 | * @param activity 175 | * @return 176 | */ 177 | public static Activity getRootContext(Activity activity) { 178 | while(activity.getParent() != null){ 179 | activity = activity.getParent(); 180 | } 181 | return activity; 182 | } 183 | /** 184 | * 判断横竖屏 185 | * @param context 186 | * @return true为横屏,false为竖屏 187 | */ 188 | public static boolean isOrientationLandscape(Context context) 189 | { 190 | return getWidthPixels(context) > getHeightPixels(context); 191 | } 192 | /** 193 | * 根据手机的分辨率从 dp 的单位 转成为 px(像素) 194 | * @param context 195 | * @param dpValue 196 | * @return 197 | */ 198 | public static int dipToPx(Context context, float dpValue) { 199 | final float density = context.getResources().getDisplayMetrics().density; 200 | return (int) (dpValue * density + 0.5f); 201 | } 202 | /** 203 | * 根据手机的分辨率从 px(像素)的单位 转成为 dp 204 | * @param context 205 | * @param pxValue 206 | * @return 207 | */ 208 | public static int pxToDip(Context context,float pxValue){ 209 | final float density = context.getResources().getDisplayMetrics().density; 210 | return (int)(pxValue/density +0.5f); 211 | 212 | } 213 | /** 214 | * px转sp 215 | * @param context 216 | * @param pxValue 217 | * @return 218 | */ 219 | public static int px2sp(Context context, float pxValue) { 220 | final float scale = context.getResources().getDisplayMetrics().density; 221 | return (int) (pxValue / scale + 0.5f); 222 | } 223 | /** 224 | * sp转px 225 | * @param context 226 | * @param spValue 227 | * @return 228 | */ 229 | public static int sp2px(Context context, float spValue) { 230 | final float scale = context.getResources().getDisplayMetrics().density; 231 | return (int) (spValue * scale + 0.5f); 232 | } 233 | 234 | /** 235 | * 隐藏输入法 236 | * @param act 237 | */ 238 | public static void hideInputMethod(Activity act) { 239 | if(act == null){ 240 | return; 241 | } 242 | try { 243 | InputMethodManager inputMethodManager = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); 244 | if (act.getWindow() != null) { 245 | if (act.getWindow().getCurrentFocus() == null) { 246 | inputMethodManager.hideSoftInputFromWindow(null, 0); 247 | } else { 248 | inputMethodManager.hideSoftInputFromWindow(act.getWindow().getCurrentFocus().getWindowToken(), 0); 249 | } 250 | } 251 | } catch (Exception e) { 252 | PrintUtils.println(e); 253 | } 254 | } 255 | 256 | public static void hideInputMethod(Activity act, EditText etext) { 257 | try { 258 | InputMethodManager inputMethodManager = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); 259 | inputMethodManager.hideSoftInputFromWindow(etext.getWindowToken(),0); 260 | } catch (Exception e) { 261 | PrintUtils.println(e); 262 | } 263 | } 264 | 265 | /** 266 | * 显示输入法 267 | * @param act 268 | */ 269 | public static void showInputMethod(Activity act){ 270 | if(act == null){ 271 | return; 272 | } 273 | try { 274 | InputMethodManager inputMethodManager = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); 275 | if (act.getWindow() != null && act.getWindow().getCurrentFocus() != null) { 276 | inputMethodManager.showSoftInputFromInputMethod(act.getWindow().getCurrentFocus().getWindowToken(), 0); 277 | } 278 | } catch (Exception e) { 279 | PrintUtils.println(e); 280 | } 281 | } 282 | 283 | /** 284 | * 显示输入法(建议使用这个,这个更能确保弹出) 285 | * @param act 286 | * @param mEditView 287 | */ 288 | public static void showInputMethod(Activity act, View mEditView) { 289 | try { 290 | mEditView.requestFocus(); 291 | InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); 292 | imm.showSoftInput(mEditView, 0); 293 | } catch (Exception e) { 294 | PrintUtils.println(e); 295 | } 296 | } 297 | 298 | /** 299 | * 隐藏标题栏(用于没有在配置里设置主题为Theme.Black.NoTitleBar) 300 | * @param act 301 | */ 302 | public static void hideFeatureTitle(Activity act){ 303 | act.requestWindowFeature(Window.FEATURE_NO_TITLE); 304 | } 305 | /** 306 | * 打电话,调用前先验证phone,同时检查是否有添加权限 307 | * @param context 308 | * @param phone 309 | */ 310 | public static void callNumber(Context context, String phone) { 311 | try { 312 | // 传入服务, parse()解析号码 313 | Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + phone)); 314 | // 通知activtity处理传入的call服务 315 | context.startActivity(intent); 316 | } catch (Exception ex) { 317 | PrintUtils.println(ex); 318 | } 319 | } 320 | /** 321 | * 判断GPS是否开启,GPS或者AGPS开启一个就认为是开启的 322 | * 323 | * @param context 324 | * @return true 表示开启 325 | */ 326 | public static final boolean isGPSOpen(final Context context) { 327 | LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 328 | // 通过GPS卫星定位,定位级别可以精确到街(通过24颗卫星定位,在室外和空旷的地方定位准确、速度快) 329 | boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); 330 | // 通过WLAN或移动网络(3G/2G)确定的位置(也称作AGPS,辅助GPS定位。主要用于在室内或遮盖物(建筑群或茂密的深林等)密集的地方定位) 331 | boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); 332 | if (gps || network) { 333 | return true; 334 | } 335 | 336 | return false; 337 | } 338 | /** 339 | * 强制帮用户打开GPS 340 | * @param context 341 | */ 342 | public static final void openGPS(Context context) { 343 | Intent GPSIntent = new Intent(); 344 | GPSIntent.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); 345 | GPSIntent.addCategory("android.intent.category.ALTERNATIVE"); 346 | GPSIntent.setData(Uri.parse("custom:3")); 347 | try { 348 | PendingIntent.getBroadcast(context, 0, GPSIntent, 0).send(); 349 | } catch (CanceledException e) { 350 | PrintUtils.println(e); 351 | } 352 | } 353 | /** 354 | * 判断wifi是否可用 355 | * 356 | * @param context 357 | * @return 358 | */ 359 | public static boolean isWifiAvilable(Context context) { 360 | ConnectivityManager manager = (ConnectivityManager) context 361 | .getSystemService(Context.CONNECTIVITY_SERVICE); 362 | if (manager == null) { 363 | return false; 364 | } 365 | NetworkInfo wifiInfo = manager 366 | .getNetworkInfo(ConnectivityManager.TYPE_WIFI); 367 | if (wifiInfo != null) { 368 | return wifiInfo.isAvailable(); 369 | } 370 | return false; 371 | } 372 | 373 | /** 374 | * 判断wifi是否处于连接 375 | * @param context 376 | * @return 377 | */ 378 | public static boolean isWiftConnected(Context context){ 379 | ConnectivityManager manager = (ConnectivityManager) context 380 | .getSystemService(Context.CONNECTIVITY_SERVICE); 381 | if (manager == null) { 382 | return false; 383 | } 384 | NetworkInfo wifiInfo = manager 385 | .getNetworkInfo(ConnectivityManager.TYPE_WIFI); 386 | if(wifiInfo != null){ 387 | return wifiInfo.isConnected(); 388 | } 389 | return false; 390 | } 391 | /** 392 | * 判断网络是否可用 393 | * 394 | * @param context 395 | * @return 396 | */ 397 | public static boolean isNetworkAvilable(Context context) { 398 | if (context == null) { 399 | return false; 400 | } 401 | try { 402 | ConnectivityManager con = (ConnectivityManager) context 403 | .getSystemService(Context.CONNECTIVITY_SERVICE); 404 | if(con == null){ 405 | return false; 406 | } 407 | boolean wifi=con.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnectedOrConnecting(); 408 | boolean internet=con.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).isConnectedOrConnecting(); 409 | boolean isAvilable = con.getActiveNetworkInfo().isAvailable(); 410 | if(wifi || internet || isAvilable){ 411 | return true; 412 | } 413 | // if (connectivity != null) { 414 | // // 获取网络连接管理的对象 415 | // NetworkInfo info = connectivity.getActiveNetworkInfo(); 416 | // if (info != null) { 417 | // // 判断当前网络是否已经连接 418 | // return info.isConnected() 419 | // } 420 | // } 421 | } catch (Exception e) { 422 | PrintUtils.println(e); 423 | return false; 424 | } 425 | return false; 426 | } 427 | /** 428 | * 判断网络是否链接成功 429 | * @param context 430 | * @return 431 | */ 432 | public static boolean hasInternet(Context context){ 433 | ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 434 | NetworkInfo info = manager.getActiveNetworkInfo(); 435 | if (info != null && info.isConnected()) { 436 | if (info.getState() == NetworkInfo.State.CONNECTED) { 437 | return true; 438 | } 439 | } 440 | return false; 441 | } 442 | public static boolean exterStorageReady() { 443 | return Environment.MEDIA_MOUNTED.equals(Environment 444 | .getExternalStorageState()) || !Environment.isExternalStorageRemovable(); 445 | } 446 | /** 447 | * 获取缓存路径 448 | * @param context 449 | * @return 450 | */ 451 | public static String getDiskCacheDir(Context context){ 452 | String cachePath = null; 453 | try { 454 | if(exterStorageReady()){ 455 | cachePath = context.getExternalCacheDir().getPath(); 456 | }else{ 457 | cachePath = context.getCacheDir().getPath(); 458 | } 459 | } catch (Exception e) { 460 | cachePath = getCacheDir(context); 461 | e.printStackTrace(); 462 | } 463 | return cachePath; 464 | } 465 | 466 | /** 467 | * 获取文件目录 468 | * @param context 469 | * @return 470 | */ 471 | public static String getDiskFileDir(Context context){ 472 | String filePath = null; 473 | try { 474 | if(exterStorageReady()){ 475 | filePath = context.getExternalFilesDir(null).getPath(); 476 | }else { 477 | filePath = context.getFilesDir().getPath(); 478 | } 479 | } catch (Exception e) { 480 | filePath = getFileDir(context); 481 | e.printStackTrace(); 482 | } 483 | return filePath; 484 | } 485 | /** 486 | * 获取缓存目录 487 | * 488 | * @param context 489 | * @return 490 | */ 491 | public static String getCacheDir(Context context) { 492 | return context == null ? "" : context.getCacheDir().getAbsolutePath(); 493 | } 494 | 495 | /** 496 | * 获取文件缓存目录 497 | * 498 | * @param context 499 | * @return 500 | */ 501 | public static String getFileDir(Context context) { 502 | return context == null ? "" : context.getFilesDir().getAbsolutePath(); 503 | } 504 | public static String getAvilablePath(Context context){ 505 | if(exterStorageReady()){ 506 | return getExCardPath(); 507 | }else{ 508 | return getFileDir(context); 509 | } 510 | } 511 | /** 512 | * 获取扩展卡路径 513 | */ 514 | public static String getExCardPath() { 515 | if (exterStorageReady()){ 516 | return Environment.getExternalStorageDirectory().toString(); 517 | } 518 | return ""; 519 | } 520 | 521 | /** 522 | * 获取手机剩余空间 523 | * @return 524 | */ 525 | public static long getRemainSaveSize() { 526 | //有sd卡 527 | if (exterStorageReady()) { 528 | String sdcard = Environment.getExternalStorageDirectory().getPath(); 529 | StatFs statFs = new StatFs(sdcard); 530 | // 获取一个文件的存储大小 531 | long blockSize = statFs.getBlockSize(); 532 | // 获取剩下可用的文件大小 533 | long blocks = statFs.getFreeBlocks(); 534 | return blocks * blockSize; 535 | } 536 | //木有sd卡 537 | else { 538 | String rootPath = Environment.getRootDirectory().getPath(); 539 | StatFs statFs = new StatFs(rootPath); 540 | // 获取一个文件的存储大小 541 | long blockSize = statFs.getBlockSize(); 542 | // 获取剩下可用的文件大小 543 | long blocks = statFs.getFreeBlocks(); 544 | return blocks * blockSize; 545 | } 546 | } 547 | /** 548 | * 判断某应用是否安装 549 | * @param context 550 | * @param pagckageName 551 | * @return 552 | */ 553 | public boolean isAppAvilible(Context context,String pagckageName){ 554 | if(pagckageName == null || "".equals(pagckageName)){ 555 | return false; 556 | } 557 | PackageManager packageManager = context.getPackageManager(); 558 | //获取所有已安装包信息 559 | List pagckageInfos = packageManager.getInstalledPackages(0); 560 | for (int i = 0; i < pagckageInfos.size(); i++) { 561 | if(pagckageInfos.get(i).packageName.equalsIgnoreCase(pagckageName)){ 562 | return true; 563 | } 564 | } 565 | return false; 566 | } 567 | /** 568 | * 跳转到包名对应应用的启动页。 569 | * @param context 570 | * @param pagckageName 571 | * @param activityName 572 | */ 573 | public static void goApplication(Context context,String pagckageName,String activityName){ 574 | Intent it = new Intent(); 575 | ComponentName cn = new ComponentName(pagckageName,activityName); 576 | it.setComponent(cn); 577 | context.startActivity(it); 578 | } 579 | /** 580 | * 根据包名打开应用 581 | * @param context 582 | * @param pagckageName 583 | */ 584 | public static void goApplication(Context context,String pagckageName){ 585 | PackageManager packageManager = context.getPackageManager(); 586 | Intent it = packageManager.getLaunchIntentForPackage(pagckageName); 587 | context.startActivity(it); 588 | } 589 | /** 590 | * 用来判断服务是否运行. 591 | * @param context 592 | * @param className 判断的服务名字:包名+类名 593 | * @return true 在运行, false 不在运行 594 | */ 595 | public static boolean isServiceRunning(Context context,String className) { 596 | if(context == null || TextUtils.isEmpty(className)){ 597 | return false; 598 | } 599 | 600 | boolean isRunning = false; 601 | ActivityManager activityManager = 602 | (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); 603 | List serviceList 604 | = activityManager.getRunningServices(Integer.MAX_VALUE); 605 | if (!(serviceList.size()>0)) { 606 | return false; 607 | } 608 | for (int i=0; i 0) { 668 | int len = str.length(); 669 | float[] widths = new float[len]; 670 | paint.getTextWidths(str, widths); 671 | for (int j = 0; j < len; j++) { 672 | iRet += (int) Math.ceil(widths[j]); 673 | } 674 | } 675 | return iRet; 676 | } 677 | /** 678 | * 获取配置文件里的版本名称 679 | * @param context 680 | * @return 681 | */ 682 | public static String getVersionName(Context context) 683 | { 684 | PackageManager packageManager = context.getPackageManager(); 685 | //GetPackageName () is your current class package name, 0 stands for is to get version information 686 | PackageInfo packInfo; 687 | try { 688 | packInfo = packageManager.getPackageInfo(context.getPackageName(), 0); 689 | return packInfo.versionName; 690 | } catch (NameNotFoundException e) { 691 | PrintUtils.println(e); 692 | return ""; 693 | } 694 | } 695 | /** 696 | * 获取配置文件里的版本号 697 | * @param context 698 | * @return 699 | */ 700 | public static int getVersionCode(Context context) 701 | { 702 | PackageManager packageManager = context.getPackageManager(); 703 | //GetPackageName () is your current class package name, 0 stands for is to get version information 704 | PackageInfo packInfo; 705 | try { 706 | packInfo = packageManager.getPackageInfo(context.getPackageName(), 0); 707 | return packInfo.versionCode; 708 | } catch (NameNotFoundException e) { 709 | PrintUtils.println(e); 710 | return 0; 711 | } 712 | } 713 | /** 714 | * 获取metaData数据集 715 | * @param context 716 | * @return 717 | */ 718 | public static Bundle getMetaData(Context context){ 719 | ApplicationInfo info; 720 | Bundle bundle; 721 | try { 722 | info = context.getPackageManager().getApplicationInfo( 723 | context.getPackageName(), PackageManager.GET_META_DATA); 724 | bundle = info.metaData; 725 | if(bundle == null){ 726 | bundle = new Bundle(); 727 | } 728 | } catch (NameNotFoundException e) { 729 | bundle = new Bundle(); 730 | e.printStackTrace(); 731 | } 732 | return bundle; 733 | } 734 | /** 735 | * 获取设备id(IMEI number) 736 | * @param context 737 | * @return 738 | */ 739 | public static String getDeviceID(Context context) 740 | { 741 | String id = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId(); 742 | if(TextUtils.isEmpty(id)){ 743 | id = "000000000000000"; 744 | } 745 | return id; 746 | } 747 | /** 748 | * 获取设备的UA 749 | * 750 | * @param context 751 | * @return 752 | */ 753 | public static String getUserAgent(Context context) { 754 | try { 755 | WebView webview = new WebView(context); 756 | webview.layout(0, 0, 0, 0); 757 | WebSettings settings = webview.getSettings(); 758 | String ua = settings.getUserAgentString(); 759 | return ua; 760 | } catch (Exception e) { 761 | PrintUtils.println(e); 762 | } 763 | return null; 764 | } 765 | /** 766 | * 去设置默认浏览器 767 | * @param context 768 | */ 769 | public static void setDefaultBrowser(Context context,String tip){ 770 | Intent intent = new Intent(); 771 | intent.setAction("android.intent.action.VIEW"); 772 | intent.addCategory("android.intent.category.BROWSABLE"); 773 | intent.setData(Uri.parse(tip)); 774 | intent.setComponent(new ComponentName("android","com.android.internal.app.ResolverActivity")); 775 | context.startActivity(intent); 776 | } 777 | /** 778 | * 检查是否已经有了默认浏览器 779 | * @param context 780 | * @return 781 | */ 782 | public static String checkHasDefaultBrowser(Context context) { 783 | Intent intent = new Intent(Intent.ACTION_VIEW); 784 | intent.setData(Uri.parse("http://www.baidu.com")); 785 | PackageManager pm = context.getPackageManager(); 786 | ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); 787 | return info.activityInfo.packageName; 788 | } 789 | /** 790 | * 去清除包名所对应的默认浏览器 791 | * @param context 792 | * @param packgeName 793 | */ 794 | public static void clearDefaultBrowser(Context context,String packgeName){ 795 | Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:"+packgeName)); 796 | context.startActivity(intent); 797 | } 798 | /** 799 | * 清除从默认浏览器中自己浏览器 800 | * @param context 801 | */ 802 | public static void clearMyDefaultBrowser(Context context){ 803 | PackageManager pm = context.getPackageManager(); 804 | pm.clearPackagePreferredActivities(context.getPackageName()); 805 | } 806 | /** 807 | * 调整显示窗体 808 | * @param act 809 | * @param alpha 0.4f变暗 1f恢复亮度 810 | */ 811 | public static void ajustWindow(Activity act,float alpha){ 812 | WindowManager.LayoutParams lp= act.getWindow().getAttributes(); 813 | lp.alpha = alpha; 814 | act.getWindow().setAttributes(lp); 815 | } 816 | /** 817 | * 复制文本 818 | * @param context 819 | * @param text 820 | */ 821 | @SuppressWarnings("deprecation") 822 | public static void copyText(Context context,String text){ 823 | ClipboardManager cm =(ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 824 | cm.setText(text); 825 | } 826 | 827 | /** 828 | * 判断当前设置的语言 829 | * @return 830 | */ 831 | public static boolean IsLanguageCN() { 832 | String language = Locale.getDefault().getLanguage(); 833 | if (language.equalsIgnoreCase("zh") || language.equalsIgnoreCase("cn")) 834 | return true; 835 | return false; 836 | } 837 | 838 | /** 839 | * 随机的数值(这是一串32位的十六进制的数字,它产生重复值的概率是16的32次方之一) 840 | * @return 841 | */ 842 | public static String GetGUID() { 843 | return UUID.randomUUID().toString(); 844 | } 845 | 846 | /** 847 | * 将url转为文件名 848 | * @param url 849 | * @return 850 | */ 851 | // public static String GetUrlName(String url) { 852 | // if(TextUtils.isEmpty(url)){ 853 | // return GetGUID(); 854 | // } 855 | // 856 | // String name = URegex.Match(url.trim(), URegex.RegUrlName); 857 | // if (TextUtils.isEmpty(name)){ 858 | // return FileMD5Utils.stringMD5(url); 859 | // } 860 | // return name; 861 | // } 862 | 863 | @SuppressWarnings("rawtypes") 864 | public static void Sort(List list, final String sortKey) { 865 | Sort(list, sortKey, true); 866 | } 867 | 868 | // 排序 869 | @SuppressWarnings("rawtypes") 870 | public static void Sort(List list, final String sortKey, 871 | final boolean IsAsc) { 872 | Comparator cp = new Comparator() { 873 | private final Collator collator = Collator.getInstance(); 874 | 875 | @Override 876 | public int compare(Map map1, Map map2) { 877 | if (IsAsc) 878 | return collator.compare(map1.get(sortKey), 879 | map2.get(sortKey)); 880 | else 881 | return collator.compare(map2.get(sortKey), 882 | map1.get(sortKey)); 883 | } 884 | }; 885 | Collections.sort(list, cp); 886 | } 887 | 888 | /** 889 | * 某个APP是否有注册了一个明确的intent 890 | * @param context 891 | * @param intent 892 | * @return 893 | */ 894 | public static boolean isIntentAvailable(Context context, Intent intent) { 895 | final PackageManager packageManager = context.getPackageManager(); 896 | List list = packageManager.queryIntentActivities(intent, 897 | PackageManager.GET_ACTIVITIES); 898 | return list.size() > 0; 899 | } 900 | 901 | /** 902 | * 获取透明度 903 | * @param per 904 | * @return 905 | */ 906 | public static int alphaTranslate(int per){ 907 | return 255*per/100; 908 | } 909 | 910 | /** 911 | * 从assets获取bitmap 912 | * @param context 913 | * @param path 914 | * @return 915 | */ 916 | public static Bitmap createFromAsset(Context context,String path){ 917 | if(!TextUtils.isEmpty(path)){ 918 | try{ 919 | InputStream is = context.getAssets().open(path); 920 | return BitmapFactory.decodeStream(is); 921 | }catch (Exception e){ 922 | } 923 | } 924 | return null; 925 | } 926 | /** 927 | * url编码 928 | * @param url 929 | * @return 930 | */ 931 | public static String urlEncode(String url){ 932 | try { 933 | url = URLEncoder.encode(url, "UTF-8"); 934 | } catch (UnsupportedEncodingException e) { 935 | e.printStackTrace(); 936 | } 937 | return url; 938 | } 939 | /** 940 | * url解码 941 | * @param url 942 | * @return 943 | */ 944 | public static String urlDecode(String url){ 945 | try { 946 | url = URLDecoder.decode(url, "UTF-8"); 947 | } catch (UnsupportedEncodingException e) { 948 | e.printStackTrace(); 949 | } 950 | return url; 951 | } 952 | 953 | /** 954 | * 把文件扫描进媒体库 955 | * @param filename 956 | * @param context 957 | */ 958 | public static void updateMedia(String filename,Context context)//filename是我们的文件全名,包括后缀哦 959 | { 960 | MediaScannerConnection.scanFile(context, 961 | new String[]{filename}, null, 962 | new MediaScannerConnection.OnScanCompletedListener() { 963 | public void onScanCompleted(String path, Uri uri) { 964 | Log.i("ExternalStorage", "Scanned " + path + ":"); 965 | Log.i("ExternalStorage", "-> uri=" + uri); 966 | } 967 | }); 968 | } 969 | /** 970 | * 获取系统资源 971 | * @param intent 972 | * @param context 973 | * @param type 974 | * @return 975 | */ 976 | public static String getDataPath(Intent intent, Context context, String type){ 977 | String filePath = null; 978 | Uri uri = intent.getData(); 979 | String[] proj = null; 980 | boolean isTypeEmpty = TextUtils.isEmpty(type); 981 | if(isTypeEmpty){ 982 | type = "_data"; 983 | } 984 | // System.out.println("uri:"+uri); 985 | proj = new String[]{type}; 986 | Cursor actualimagecursor = context.getContentResolver().query(uri, proj, null, null, null); 987 | if(actualimagecursor == null){ 988 | filePath = uri.getPath(); 989 | }else{ 990 | actualimagecursor.moveToFirst(); 991 | int actual_image_column_index = actualimagecursor 992 | .getColumnIndexOrThrow(type); 993 | System.out.println("actual_image_column_index:"+actual_image_column_index); 994 | filePath = actualimagecursor.getString(actual_image_column_index); 995 | // if(filePath == null){ 996 | // ToastUtils.showShortMsg(context.getApplicationContext(),"路径存在问题,请确认选择方式是否存在问题!"); 997 | // } 998 | // if(filePath == null){ 999 | // filePath = uri.getPath(); 1000 | // } 1001 | // System.out.println("filePath:"+filePath); 1002 | } 1003 | return filePath; 1004 | } 1005 | } 1006 | -------------------------------------------------------------------------------- /app/src/main/java/cn/rosen/sticktextview/utils/PrintUtils.java: -------------------------------------------------------------------------------- 1 | package cn.rosen.sticktextview.utils; 2 | 3 | //import java.lang.reflect.Array; 4 | /** 5 | * 这是一个io工具类,提供了一些常用的处理文件io函数 6 | * @author Rosen 7 | * @version 1.5 8 | */ 9 | public class PrintUtils { 10 | 11 | public static boolean Debug = true; 12 | public PrintUtils() { 13 | } 14 | public static void println(Object obj){ 15 | if(Debug){ 16 | System.out.println(obj == null ? null:obj); 17 | } 18 | } 19 | public static void println(Throwable e){ 20 | if(Debug){ 21 | if(e != null){ 22 | e.printStackTrace(); 23 | } 24 | } 25 | } 26 | 27 | public static void println(){ 28 | System.out.println(); 29 | } 30 | 31 | public static void print(Object obj){ 32 | if(Debug){ 33 | System.out.print(obj == null ? null:obj); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/cn/rosen/sticktextview/view/StickerTextView.java: -------------------------------------------------------------------------------- 1 | package cn.rosen.sticktextview.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.DashPathEffect; 9 | import android.graphics.Matrix; 10 | import android.graphics.Paint; 11 | import android.graphics.PaintFlagsDrawFilter; 12 | import android.graphics.Path; 13 | import android.graphics.PathEffect; 14 | import android.graphics.PointF; 15 | import android.graphics.RectF; 16 | import android.support.v4.view.MotionEventCompat; 17 | import android.text.TextUtils; 18 | import android.util.AttributeSet; 19 | import android.util.DisplayMetrics; 20 | import android.util.TypedValue; 21 | import android.view.MotionEvent; 22 | import android.view.View; 23 | import android.widget.TextView; 24 | 25 | import java.util.LinkedHashMap; 26 | 27 | import cn.rosen.sticktextview.R; 28 | 29 | 30 | /** 31 | * Created by sam on 14-8-14. 32 | */ 33 | public class StickerTextView extends View { 34 | 35 | private String TAG = "StickerView"; 36 | 37 | public static final float MAX_SCALE_SIZE = 10;//3.2f; 38 | 39 | public static final float MIN_SCALE_SIZE = 0;//0.6f; 40 | 41 | private float[] mOriginPoints; 42 | private float[] mPoints; 43 | private RectF mOriginContentRect; 44 | private RectF mContentRect; 45 | private RectF mViewRect; 46 | 47 | 48 | private PointF mid = new PointF(); 49 | 50 | private float mLastPointX, mLastPointY; 51 | private Bitmap mBitmap; 52 | private Bitmap originBitmap; 53 | 54 | private Bitmap mControllerBitmap, mDeleteBitmap; 55 | private Bitmap mCopyBitmap; 56 | private Matrix mMatrix; 57 | private Paint mPaint, mBorderPaint; 58 | private float mControllerWidth, mControllerHeight, mDeleteWidth, mDeleteHeight, mCopyWidth, mCopyHeight; 59 | 60 | private boolean mInController, mInMove; 61 | private boolean mDrawController = true; 62 | private float mStickerScaleSize = 1.0f; 63 | private DisplayMetrics dm; 64 | private boolean isMove; 65 | 66 | private float oldx1, oldy1, oldx2, oldy2; 67 | private float roatetAngle = 0; 68 | 69 | private OnStickerTextTouchListener mOnStickerTouchListener; 70 | private Paint mTextPaint; 71 | 72 | //存放文字的集合 73 | private LinkedHashMap bank = new LinkedHashMap<>(); 74 | private int imageCount; 75 | 76 | 77 | public StickerTextView(Context context) { 78 | this(context, null); 79 | } 80 | 81 | public StickerTextView(Context context, AttributeSet attrs) { 82 | this(context, attrs, 0); 83 | } 84 | 85 | public StickerTextView(Context context, AttributeSet attrs, int defStyle) { 86 | super(context, attrs, defStyle); 87 | init(); 88 | } 89 | 90 | private void init() { 91 | mPaint = new Paint(); 92 | mPaint.setAntiAlias(true); 93 | mPaint.setFilterBitmap(true); 94 | mPaint.setStyle(Paint.Style.STROKE); 95 | mPaint.setStrokeWidth(4.0f); 96 | mPaint.setColor(Color.WHITE); 97 | //文字边框的画笔 98 | mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 99 | mTextPaint.setStyle(Paint.Style.STROKE); 100 | mTextPaint.setColor(Color.parseColor("#ff7700")); 101 | mTextPaint.setStrokeWidth(4); 102 | PathEffect effects = new DashPathEffect(new float[]{dpToPx(8), dpToPx(4)}, 1); 103 | mTextPaint.setPathEffect(effects); 104 | 105 | dm = getResources().getDisplayMetrics(); 106 | //边框的画笔 107 | mBorderPaint = new Paint(mPaint); 108 | mBorderPaint.setColor(Color.BLACK);//#B2ffffff 109 | mBorderPaint.setShadowLayer(dpToPx(2.0f), 0, 0, Color.parseColor("#33000000")); 110 | 111 | mControllerBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.edit_wz_bg_btn3); 112 | mControllerWidth = mControllerBitmap.getWidth(); 113 | mControllerHeight = mControllerBitmap.getHeight(); 114 | 115 | mCopyBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.edit_wz_bg_btn2); 116 | mCopyWidth = mCopyBitmap.getWidth(); 117 | mCopyHeight = mCopyBitmap.getHeight(); 118 | 119 | mDeleteBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.edit_wz_bg_btn1); 120 | mDeleteWidth = mDeleteBitmap.getWidth(); 121 | mDeleteHeight = mDeleteBitmap.getHeight(); 122 | 123 | } 124 | 125 | public void setBackgroundBitmap(Bitmap bitmap) { 126 | if (bitmap != null) { 127 | originBitmap = bitmap; 128 | mBitmap = originBitmap.copy(Bitmap.Config.ARGB_8888, true); 129 | } else { 130 | mBitmap = bitmap; 131 | } 132 | setFocusable(true); 133 | try { 134 | if (mBitmap != null) { 135 | float px = mBitmap.getWidth(); 136 | float py = mBitmap.getHeight(); 137 | mOriginPoints = new float[]{0, 0, px, 0, px, py, 0, py, px / 2, py / 2}; 138 | mPoints = new float[10]; 139 | mOriginContentRect = new RectF(0, 0, px, py); 140 | mContentRect = new RectF(); 141 | 142 | mMatrix = new Matrix(); 143 | float transtLeft = ((float) dm.widthPixels - px) / 2; 144 | float transtTop = ((float) dm.heightPixels - py) / 2; 145 | 146 | mMatrix.postTranslate(transtLeft, transtTop); 147 | } 148 | } catch (Exception e) { 149 | e.printStackTrace(); 150 | } 151 | postInvalidate(); 152 | } 153 | 154 | /** 155 | * 添加textView 156 | * 157 | * @param textView textview对象 158 | * @param left 左边界 159 | * @param top 上边界 160 | * @param right 右边界 161 | * @param bottom 下边界 162 | * @param isSingleLine 是否单行,单行可自动调节边框大小 163 | * @param nums 字数限制 164 | */ 165 | public void addTextDraw(TextView textView, float left, float top, float right, float bottom, boolean isSingleLine, int nums) { 166 | float x = dpToPx(left); 167 | float y = dpToPx(top); 168 | float r = dpToPx(right); 169 | float b = dpToPx(bottom); 170 | RectF mOriginTextRect; 171 | textView.setLeft((int) x); 172 | textView.setTop((int) y); 173 | if (isSingleLine) { 174 | textView.setRight((int) (getCharacterWidth(textView) + textView.getLeft())); 175 | mOriginTextRect = new RectF(x, y, (int) (getCharacterWidth(textView) + textView.getLeft()), b); 176 | } else { 177 | textView.setRight((int) r); 178 | mOriginTextRect = new RectF(x, y, r, b); 179 | } 180 | textView.setBottom((int) b); 181 | Canvas canvas = new Canvas(); 182 | if (canvas != null) { 183 | canvas.translate(x, y); 184 | } 185 | canvas.setBitmap(mBitmap); 186 | bank.put(++imageCount, new TextViewItem(textView, canvas, isSingleLine, mOriginTextRect, nums)); 187 | postInvalidate(); 188 | } 189 | 190 | /** 191 | * 更新文字 192 | * 193 | * @param textView 194 | * @param isSingLine 195 | */ 196 | public void updateTextDraw(TextView textView, boolean isSingLine) { 197 | if (isSingLine) { 198 | textView.setRight((int) (getCharacterWidth(textView) + textView.getLeft())); 199 | } 200 | invalidate(); 201 | } 202 | 203 | //方法入口 204 | public float getCharacterWidth(TextView tv) { 205 | if (null == tv) return 0f; 206 | return getCharacterWidth(tv.getText().toString(), tv.getTextSize()) * tv.getScaleX(); 207 | } 208 | 209 | //获取每个字符的宽度主方法: 210 | public float getCharacterWidth(String text, float size) { 211 | if (null == text || "".equals(text)) 212 | return 0; 213 | Paint paint = new Paint(); 214 | paint.setTextSize(size); 215 | float text_width = paint.measureText(text);//得到总体长度 216 | return text_width; 217 | } 218 | 219 | public Matrix getMarkMatrix() { 220 | return mMatrix; 221 | } 222 | 223 | @Override 224 | public void setFocusable(boolean focusable) { 225 | super.setFocusable(focusable); 226 | postInvalidate(); 227 | } 228 | 229 | @Override 230 | protected void onDraw(Canvas canvas) { 231 | super.onDraw(canvas); 232 | if (mBitmap == null || mMatrix == null) { 233 | return; 234 | } 235 | //背景和边框映射 236 | mMatrix.mapPoints(mPoints, mOriginPoints); 237 | mMatrix.mapRect(mContentRect, mOriginContentRect); 238 | mBitmap = originBitmap.copy(Bitmap.Config.ARGB_8888, true); 239 | for (Integer id : bank.keySet()) { 240 | TextViewItem textViewItem = bank.get(id); 241 | int left = textViewItem.getTextView().getLeft(); 242 | int top = textViewItem.getTextView().getTop(); 243 | int right = textViewItem.getTextView().getRight(); 244 | int bottom = textViewItem.getTextView().getBottom(); 245 | float[] mOriginTextPoints; 246 | float[] mTextPoints = new float[8]; 247 | if (TextUtils.isEmpty(textViewItem.getTextView().getText())) { 248 | RectF rectF = textViewItem.getmOriginTextRect(); 249 | mOriginTextPoints = new float[]{rectF.left, rectF.top, rectF.right, rectF.top, rectF.right, rectF.bottom, rectF.left, rectF.bottom}; 250 | } else { 251 | mOriginTextPoints = new float[]{left, top, right, top, right, bottom, left, bottom}; 252 | } 253 | //文字和边框映射 254 | mMatrix.mapPoints(mTextPoints, mOriginTextPoints); 255 | if (mDrawController) { 256 | Path path = new Path(); 257 | path.moveTo(mTextPoints[0], mTextPoints[1]); 258 | path.lineTo(mTextPoints[2], mTextPoints[3]); 259 | canvas.drawPath(path, mTextPaint); 260 | path.moveTo(mTextPoints[2], mTextPoints[3]); 261 | path.lineTo(mTextPoints[4], mTextPoints[5]); 262 | canvas.drawPath(path, mTextPaint); 263 | path.moveTo(mTextPoints[4], mTextPoints[5]); 264 | path.lineTo(mTextPoints[6], mTextPoints[7]); 265 | canvas.drawPath(path, mTextPaint); 266 | path.moveTo(mTextPoints[6], mTextPoints[7]); 267 | path.lineTo(mTextPoints[0], mTextPoints[1]); 268 | canvas.drawPath(path, mTextPaint); 269 | } 270 | //画字体 271 | textViewItem.getCanvas().setBitmap(mBitmap); 272 | textViewItem.getCanvas().setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG)); 273 | textViewItem.getTextView().draw(textViewItem.getCanvas()); 274 | } 275 | //花边框和控制点 276 | if (mDrawController) { 277 | canvas.drawLine(mPoints[0], mPoints[1], mPoints[2], mPoints[3], mBorderPaint); 278 | canvas.drawLine(mPoints[2], mPoints[3], mPoints[4], mPoints[5], mBorderPaint); 279 | canvas.drawLine(mPoints[4], mPoints[5], mPoints[6], mPoints[7], mBorderPaint); 280 | canvas.drawLine(mPoints[6], mPoints[7], mPoints[0], mPoints[1], mBorderPaint); 281 | canvas.drawBitmap(mControllerBitmap, mPoints[4] - mControllerWidth / 2, mPoints[5] - mControllerHeight / 2, mBorderPaint); 282 | canvas.drawBitmap(mCopyBitmap, mPoints[2] - mCopyWidth / 2, mPoints[3] - mCopyHeight / 2, mBorderPaint); 283 | canvas.drawBitmap(mDeleteBitmap, mPoints[0] - mDeleteWidth / 2, mPoints[1] - mDeleteHeight / 2, mBorderPaint); 284 | } 285 | canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG)); 286 | canvas.drawBitmap(mBitmap, mMatrix, mPaint); 287 | } 288 | 289 | 290 | private float dpToPx(float pt) { 291 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, pt, getResources().getDisplayMetrics()); 292 | } 293 | 294 | public void setShowDrawController(boolean show) { 295 | mDrawController = show; 296 | invalidate(); 297 | } 298 | 299 | 300 | private boolean isInController(float x, float y) { 301 | int position = 4; 302 | //while (position < 8) { 303 | float rx = mPoints[position]; 304 | float ry = mPoints[position + 1]; 305 | RectF rectF = new RectF(rx - mControllerWidth / 2, 306 | ry - mControllerHeight / 2, 307 | rx + mControllerWidth / 2, 308 | ry + mControllerHeight / 2); 309 | if (rectF.contains(x, y)) { 310 | return true; 311 | } 312 | return false; 313 | 314 | } 315 | 316 | private boolean isInDelete(float x, float y) { 317 | int position = 0; 318 | //while (position < 8) { 319 | float rx = mPoints[position]; 320 | float ry = mPoints[position + 1]; 321 | RectF rectF = new RectF(rx - mDeleteWidth / 2, 322 | ry - mDeleteHeight / 2, 323 | rx + mDeleteWidth / 2, 324 | ry + mDeleteHeight / 2); 325 | if (rectF.contains(x, y)) { 326 | return true; 327 | } 328 | return false; 329 | 330 | } 331 | 332 | private boolean isInCopy(float x, float y) { 333 | int position = 2; 334 | float rx = mPoints[position]; 335 | float ry = mPoints[position + 1]; 336 | RectF rectF = new RectF(rx - mCopyWidth / 2, 337 | ry - mCopyHeight / 2, 338 | rx + mCopyWidth / 2, 339 | ry + mCopyHeight / 2); 340 | if (rectF.contains(x, y)) { 341 | return true; 342 | } 343 | return false; 344 | } 345 | 346 | private boolean mInDelete = false; 347 | 348 | @Override 349 | public boolean dispatchTouchEvent(MotionEvent event) { 350 | return super.dispatchTouchEvent(event); 351 | } 352 | 353 | @Override 354 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 355 | 356 | } 357 | 358 | @Override 359 | public boolean onTouchEvent(MotionEvent event) { 360 | if (mOnStickerTouchListener != null) { 361 | mOnStickerTouchListener.onTextMoveToHead(this); 362 | } 363 | if (mViewRect == null) { 364 | mViewRect = new RectF(0f, 0f, getMeasuredWidth(), getMeasuredHeight()); 365 | } 366 | if (event.getPointerCount() == 1)//单点触控 367 | { 368 | float x = event.getX(); 369 | float y = event.getY(); 370 | int action = MotionEventCompat.getActionMasked(event); 371 | switch (action) { 372 | case MotionEvent.ACTION_DOWN: 373 | if (isInController(x, y)) { 374 | mInController = true; 375 | mLastPointY = y; 376 | mLastPointX = x; 377 | midPointToStartPoint(x, y); 378 | } else if (isInDelete(x, y)) { 379 | mInDelete = true; 380 | } else if (isInCopy(x, y)) { 381 | if (mOnStickerTouchListener != null) { 382 | mOnStickerTouchListener.onTextCopy(this); 383 | } 384 | } else if (mContentRect.contains(x, y)) { 385 | setShowDrawController(true); 386 | mLastPointY = y; 387 | mLastPointX = x; 388 | mInMove = true; 389 | isMove = false; 390 | 391 | //单击 392 | for (Integer id : bank.keySet()) { 393 | TextViewItem textViewItem = bank.get(id); 394 | int left = textViewItem.getTextView().getLeft(); 395 | int top = textViewItem.getTextView().getTop(); 396 | int right = textViewItem.getTextView().getRight(); 397 | int bottom = textViewItem.getTextView().getBottom(); 398 | RectF mOriginTextRect; 399 | if (TextUtils.isEmpty(textViewItem.getTextView().getText())) { 400 | mOriginTextRect = textViewItem.getmOriginTextRect(); 401 | } else { 402 | mOriginTextRect = new RectF(left, top, right, bottom); 403 | } 404 | RectF mTextRect = new RectF(); 405 | mMatrix.mapRect(mTextRect, mOriginTextRect); 406 | if (mTextRect.contains(x, y)) { 407 | //单击文字 408 | if (mOnStickerTouchListener != null) { 409 | mOnStickerTouchListener.onTextClickCurrentText(textViewItem); 410 | } 411 | } 412 | } 413 | } else { 414 | setShowDrawController(false); 415 | return false; 416 | } 417 | break; 418 | case MotionEvent.ACTION_UP: 419 | mInController = false; 420 | if (isInDelete(x, y) && mInDelete) { 421 | mInDelete = false; 422 | doDeleteSticker(); 423 | } 424 | case MotionEvent.ACTION_CANCEL: 425 | mLastPointX = 0; 426 | mLastPointY = 0; 427 | mInController = false; 428 | mInMove = false; 429 | mInDelete = false; 430 | break; 431 | case MotionEvent.ACTION_MOVE: 432 | if (mInController) { 433 | mMatrix.postRotate(rotation(event), mid.x, mid.y);//mPoints[8], mPoints[9] 434 | float nowLenght = caculateLength(mPoints[4], mPoints[5]); 435 | float touchLenght = caculateLength(event.getX(), event.getY()); 436 | if ((float) Math.sqrt((nowLenght - touchLenght) * (nowLenght - touchLenght)) > 0.0f) { 437 | float scale = touchLenght / nowLenght; 438 | float nowsc = mStickerScaleSize * scale; 439 | if (nowsc >= MIN_SCALE_SIZE && nowsc <= MAX_SCALE_SIZE) { 440 | mMatrix.postScale(scale, scale, mid.x, mid.y); 441 | mStickerScaleSize = nowsc; 442 | // Log.i(TAG, mStickerScaleSize +"缩放比例"); 443 | } 444 | } 445 | invalidate(); 446 | mLastPointX = x; 447 | mLastPointY = y; 448 | break; 449 | } 450 | if (mInMove) { //拖动的操作 451 | float cX = x - mLastPointX; 452 | float cY = y - mLastPointY; 453 | mInController = false; 454 | //判断手指抖动距离 加上isMove判断 只要移动过 都是true 455 | if (!isMove && Math.abs(cX) < 0.5f 456 | && Math.abs(cY) < 0.5f) { 457 | isMove = false; 458 | } else { 459 | isMove = true; 460 | } 461 | mMatrix.postTranslate(cX, cY); 462 | postInvalidate(); 463 | mLastPointX = x; 464 | mLastPointY = y; 465 | break; 466 | } 467 | invalidate(); 468 | return true; 469 | } 470 | } else { 471 | //多点触控 472 | switch (event.getAction() & MotionEvent.ACTION_MASK) { 473 | case MotionEvent.ACTION_POINTER_DOWN: 474 | float x1 = event.getX(0); 475 | float x2 = event.getX(1); 476 | float y1 = event.getY(0); 477 | float y2 = event.getY(1); 478 | if (mContentRect.contains(x1, y1) || mContentRect.contains(x2, y2)) { 479 | setShowDrawController(true); 480 | mInController = true; 481 | oldx1 = x1; 482 | oldy1 = y1; 483 | oldx2 = x2; 484 | oldy2 = y2; 485 | } else { 486 | setShowDrawController(false); 487 | return false; 488 | } 489 | break; 490 | case MotionEvent.ACTION_MOVE: 491 | x1 = event.getX(0); 492 | x2 = event.getX(1); 493 | y1 = event.getY(0); 494 | y2 = event.getY(1); 495 | if (mInController) { 496 | float xa = oldx2 - oldx1; 497 | float ya = oldy2 - oldy1; 498 | 499 | float xb = x2 - x1; 500 | float yb = y2 - y1; 501 | 502 | float nowLenght = (float) Math.sqrt(xa * xa + ya * ya); 503 | float touchLenght = (float) Math.sqrt(xb * xb + yb * yb); 504 | if ((float) Math.sqrt((nowLenght - touchLenght) * (nowLenght - touchLenght)) > 0.0f) { 505 | float scale = touchLenght / nowLenght; 506 | float nowsc = mStickerScaleSize * scale; 507 | if (nowsc >= MIN_SCALE_SIZE && nowsc <= MAX_SCALE_SIZE) { 508 | mMatrix.postScale(scale, scale, mContentRect.centerX(), mContentRect.centerY()); 509 | mStickerScaleSize = nowsc; 510 | //Log.i(TAG, mStickerScaleSize +"缩放比例"); 511 | } 512 | double cos = (xa * xb + ya * yb) / (nowLenght * touchLenght); 513 | if (cos > 1 || cos < -1) { 514 | return false; 515 | } 516 | float angle = (float) Math.toDegrees(Math.acos(cos)); 517 | 518 | // 拉普拉斯定理 519 | float calMatrix = xa * yb - xb * ya;// 行列式计算 确定转动方向 520 | 521 | int flag = calMatrix > 0 ? 1 : -1; 522 | angle = flag * angle; 523 | // System.out.println("angle--->" + angle); 524 | roatetAngle += angle; 525 | mMatrix.postRotate(angle, mContentRect.centerX(), mContentRect.centerY()); 526 | invalidate(); 527 | } 528 | 529 | oldx1 = x1; 530 | oldy1 = y1; 531 | oldx2 = x2; 532 | oldy2 = y2; 533 | break; 534 | } 535 | 536 | case MotionEvent.ACTION_POINTER_UP: 537 | mInController = false; 538 | mInMove = false; 539 | mInDelete = false; 540 | break; 541 | } 542 | } 543 | invalidate(); 544 | return true; 545 | } 546 | 547 | private void doDeleteSticker() { 548 | if (mOnStickerTouchListener != null) { 549 | mOnStickerTouchListener.onTextDelete(this); 550 | } 551 | } 552 | 553 | private boolean canStickerMove(float cx, float cy) { 554 | float px = cx + mPoints[8]; 555 | float py = cy + mPoints[9]; 556 | if (mViewRect.contains(px, py)) { 557 | return true; 558 | } else { 559 | return false; 560 | } 561 | } 562 | 563 | 564 | private float caculateLength(float x, float y) { 565 | float ex = x - mid.x; 566 | float ey = y - mid.y; 567 | return (float) Math.sqrt(ex * ex + ey * ey); 568 | } 569 | 570 | 571 | private float rotation(MotionEvent event) { 572 | float originDegree = calculateDegree(mLastPointX, mLastPointY); 573 | float nowDegree = calculateDegree(event.getX(), event.getY()); 574 | return nowDegree - originDegree; 575 | } 576 | 577 | private float calculateDegree(float x, float y) { 578 | double delta_x = x - mid.x; 579 | double delta_y = y - mid.y; 580 | double radians = Math.atan2(delta_y, delta_x); 581 | return (float) Math.toDegrees(radians); 582 | } 583 | 584 | public interface OnStickerTextTouchListener { 585 | void onTextCopy(StickerTextView stickerView); 586 | 587 | void onTextDelete(StickerTextView stickerView); 588 | 589 | void onTextMoveToHead(StickerTextView stickerView); 590 | 591 | void onTextClickCurrentText(TextViewItem textViewItem); 592 | } 593 | 594 | /** 595 | * 触摸的位置和图片左上角位置的中点 596 | * 597 | * @param x 598 | * @param y 599 | */ 600 | private void midPointToStartPoint(float x, float y) { 601 | float[] arrayOfFloat = new float[9]; 602 | mMatrix.getValues(arrayOfFloat); 603 | float f1 = 0.0f * arrayOfFloat[0] + 0.0f * arrayOfFloat[1] + arrayOfFloat[2]; 604 | float f2 = 0.0f * arrayOfFloat[3] + 0.0f * arrayOfFloat[4] + arrayOfFloat[5]; 605 | float f3 = f1 + x; 606 | float f4 = f2 + y; 607 | mid.set(f3 / 2, f4 / 2); 608 | } 609 | 610 | public void setOnStickerTextTouchListener(OnStickerTextTouchListener listener) { 611 | mOnStickerTouchListener = listener; 612 | } 613 | } 614 | -------------------------------------------------------------------------------- /app/src/main/java/cn/rosen/sticktextview/view/TextViewItem.java: -------------------------------------------------------------------------------- 1 | package cn.rosen.sticktextview.view; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.RectF; 5 | import android.widget.TextView; 6 | 7 | /** 8 | * Created by lenovo on 2016/6/17. 9 | */ 10 | public class TextViewItem { 11 | 12 | public int getNums() { 13 | return nums; 14 | } 15 | 16 | private final int nums; 17 | 18 | public RectF getmOriginTextRect() { 19 | return mOriginTextRect; 20 | } 21 | 22 | private final RectF mOriginTextRect; 23 | private TextView textView; 24 | private Canvas canvas; 25 | private float[] mOriginTextPoints; 26 | private float[] mTextPoints; 27 | 28 | public boolean isSingleLine() { 29 | return isSingleLine; 30 | } 31 | 32 | public void setSingleLine(boolean singleLine) { 33 | isSingleLine = singleLine; 34 | } 35 | 36 | private boolean isSingleLine; 37 | 38 | public Canvas getCanvas() { 39 | return canvas; 40 | } 41 | 42 | public void setCanvas(Canvas canvas) { 43 | this.canvas = canvas; 44 | } 45 | 46 | 47 | public float[] getmOriginTextPoints() { 48 | return mOriginTextPoints; 49 | } 50 | 51 | public void setmOriginTextPoints(float[] mOriginTextPoints) { 52 | this.mOriginTextPoints = mOriginTextPoints; 53 | } 54 | 55 | public float[] getmTextPoints() { 56 | return mTextPoints; 57 | } 58 | 59 | public void setmTextPoints(float[] mTextPoints) { 60 | this.mTextPoints = mTextPoints; 61 | } 62 | 63 | public TextView getTextView() { 64 | return textView; 65 | } 66 | 67 | public void setTextView(TextView textView) { 68 | this.textView = textView; 69 | } 70 | 71 | public TextViewItem(TextView textView, Canvas canvas, boolean isSingleLine, RectF mOriginTextRect, int nums) { 72 | this.mOriginTextRect = mOriginTextRect; 73 | this.textView = textView; 74 | this.canvas = canvas; 75 | this.isSingleLine = isSingleLine; 76 | this.nums = nums; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanbaokun/StickTextView/e0dab12f8806208d5c016f24cafa8a0d679db035/app/src/main/res/drawable/bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanbaokun/StickTextView/e0dab12f8806208d5c016f24cafa8a0d679db035/app/src/main/res/drawable/bg_test.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 16 | 17 | 23 | 30 | 38 |