├── .gitignore
├── LICENCE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── xiaopo
│ │ └── flying
│ │ └── stickerview
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── java
│ │ └── com
│ │ │ └── xiaopo
│ │ │ └── flying
│ │ │ └── stickerview
│ │ │ ├── HelloIconEvent.java
│ │ │ ├── MainActivity.java
│ │ │ └── util
│ │ │ └── FileUtil.java
│ └── res
│ │ ├── drawable-hdpi
│ │ └── ic_favorite_white_24dp.png
│ │ ├── drawable-mdpi
│ │ └── ic_favorite_white_24dp.png
│ │ ├── drawable-nodpi
│ │ ├── border.png
│ │ ├── bubble.png
│ │ ├── haizewang_215.png
│ │ ├── haizewang_23.png
│ │ └── haizewang_90.png
│ │ ├── drawable-xhdpi
│ │ └── ic_favorite_white_24dp.png
│ │ ├── drawable-xxhdpi
│ │ └── ic_favorite_white_24dp.png
│ │ ├── drawable-xxxhdpi
│ │ └── ic_favorite_white_24dp.png
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── menu
│ │ └── menu_save.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── xiaopo
│ └── flying
│ └── stickerview
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── screenshot1.png
└── screenshot2.png
├── settings.gradle
└── sticker
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── xiaopo
│ └── flying
│ └── sticker
│ ├── AbstractFlipEvent.java
│ ├── BitmapStickerIcon.java
│ ├── DeleteIconEvent.java
│ ├── DrawableSticker.java
│ ├── FlipBothDirectionsEvent.java
│ ├── FlipHorizontallyEvent.java
│ ├── FlipVerticallyEvent.java
│ ├── Sticker.java
│ ├── StickerIconEvent.java
│ ├── StickerUtils.java
│ ├── StickerView.java
│ ├── TextSticker.java
│ └── ZoomIconEvent.java
└── res
├── drawable-hdpi
├── sticker_ic_close_white_18dp.png
├── sticker_ic_flip_white_18dp.png
└── sticker_ic_scale_white_18dp.png
├── drawable-mdpi
├── sticker_ic_close_white_18dp.png
├── sticker_ic_flip_white_18dp.png
└── sticker_ic_scale_white_18dp.png
├── drawable-xhdpi
├── sticker_ic_close_white_18dp.png
├── sticker_ic_flip_white_18dp.png
└── sticker_ic_scale_white_18dp.png
├── drawable-xxhdpi
├── sticker_ic_close_white_18dp.png
├── sticker_ic_flip_white_18dp.png
└── sticker_ic_scale_white_18dp.png
├── drawable-xxxhdpi
├── sticker_ic_close_white_18dp.png
├── sticker_ic_flip_white_18dp.png
└── sticker_ic_scale_white_18dp.png
├── drawable
└── sticker_transparent_background.xml
└── values
├── attrs.xml
└── strings.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .DS_Store
5 | /build
6 | /captures
7 | .externalNativeBuild
8 | /.idea/
9 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2016 wuapnjie
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | []()
2 | StickerView
3 | =========
4 |
5 | A view which can add sticker and zoom,drag,flip,delete it
6 |
7 | **I hope you can copy the source code to your project so you can design your own function.**
8 |
9 | ## Screenshots
10 | 
11 | 
12 |
13 | ## Usage
14 |
15 | **Suggestion**
16 |
17 | **copy the source code to your project so you can design your own function.**
18 |
19 |
20 | **Tips**:StickerView extends FrameLayout
21 | #### In layout
22 | ```xml
23 |
28 |
29 |
33 |
34 | ```
35 | #### Add sticker
36 | If the sticker is drawable, it's intrinsic width and height can not be zero.
37 | If the sticker is text, you can set text color, font and alignment and the region which holds the text.
38 |
39 | ```java
40 | stickerView.addSticker(sticker)
41 | stickerView.replace(sticker)
42 | stickerView.remove(sticker)
43 | stickerView.removeCurrentSticker()
44 | stickerView.removeAllStickers()
45 | stcikerView.setLocked(true)
46 | ```
47 |
48 | Also you can custom the icon and icon event and position
49 |
50 | ```java
51 | BitmapStickerIcon heartIcon =
52 | new BitmapStickerIcon(ContextCompat.getDrawable(this, R.drawable.ic_favorite_white_24dp),
53 | BitmapStickerIcon.LEFT_BOTTOM);
54 | heartIcon.setIconEvent(new HelloIconEvent());
55 |
56 | stickerView.setIcons(Arrays.asList(deleteIcon, zoomIcon, flipIcon, heartIcon));
57 | ```
58 |
59 | ## Update
60 |
61 | * **2016/10/11** Add horizontal flip function.
62 | * **2016/10/12** Add Lock function to disable handle stickers.
63 | * **2016/11/30** Added text stickers which supports both text and image background. Thanks to [taoliuh](https://github.com/taoliuh).
64 | * **2016/12/02** Fixed the region of sticker bigger bug,and add more custom configure.
65 | * **2016/12/03** Add more callback
66 | * **2016/12/14** Add [PhotoView](https://github.com/chrisbanes/PhotoView) support.
67 | * **2016/12/15** Add remove methods.
68 | * **2016/12/16** Add Double Tap Callback
69 | * **2016/12/17** Add Constrain Sticker's move area
70 | * **2017/02/07** Custom your icon and icon event
71 | * **2017/04/25** Fix scale err and add more useful function
72 |
73 | ## Todo
74 | - [x] Constrain the sticker's moving area
75 | - [x] Add Double Tap callback
76 |
77 | ## Licence
78 |
79 | ```
80 | Copyright 2016 wuapnjie
81 |
82 | Licensed under the Apache License, Version 2.0 (the "License");
83 | you may not use this file except in compliance with the License.
84 | You may obtain a copy of the License at
85 |
86 | http://www.apache.org/licenses/LICENSE-2.0
87 |
88 | Unless required by applicable law or agreed to in writing, software
89 | distributed under the License is distributed on an "AS IS" BASIS,
90 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
91 | See the License for the specific language governing permissions and
92 | limitations under the License.
93 | ```
94 |
95 |
96 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.2"
6 | defaultConfig {
7 | applicationId "com.xiaopo.flying.stickerview"
8 | minSdkVersion 14
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
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(dir: 'libs', include: ['*.jar'])
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion"
28 | compile "com.github.chrisbanes:PhotoView:$rootProject.ext.photoViewVersion"
29 | testCompile 'junit:junit:4.12'
30 | compile project(path: ':sticker')
31 | }
32 |
--------------------------------------------------------------------------------
/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 /usr/local/android-sdk-linux/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/com/xiaopo/flying/stickerview/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.stickerview;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest {
18 | @Test public void useAppContext() throws Exception {
19 | // Context of the app under test.
20 | Context appContext = InstrumentationRegistry.getTargetContext();
21 |
22 | assertEquals("com.xiaopo.flying.stickerview", appContext.getPackageName());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/com/xiaopo/flying/stickerview/HelloIconEvent.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.stickerview;
2 |
3 | import android.view.MotionEvent;
4 | import android.widget.Toast;
5 | import com.xiaopo.flying.sticker.StickerIconEvent;
6 | import com.xiaopo.flying.sticker.StickerView;
7 |
8 | /**
9 | * @author wupanjie
10 | * @see StickerIconEvent
11 | */
12 |
13 | public class HelloIconEvent implements StickerIconEvent{
14 | @Override public void onActionDown(StickerView stickerView, MotionEvent event) {
15 |
16 | }
17 |
18 | @Override public void onActionMove(StickerView stickerView, MotionEvent event) {
19 |
20 | }
21 |
22 | @Override public void onActionUp(StickerView stickerView, MotionEvent event) {
23 | Toast.makeText(stickerView.getContext(), "Hello World!", Toast.LENGTH_SHORT).show();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xiaopo/flying/stickerview/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.stickerview;
2 |
3 | import android.Manifest;
4 | import android.content.pm.PackageManager;
5 | import android.graphics.Color;
6 | import android.graphics.drawable.Drawable;
7 | import android.os.Bundle;
8 | import android.support.annotation.NonNull;
9 | import android.support.v4.app.ActivityCompat;
10 | import android.support.v4.content.ContextCompat;
11 | import android.support.v7.app.AppCompatActivity;
12 | import android.support.v7.widget.Toolbar;
13 | import android.text.Layout;
14 | import android.util.Log;
15 | import android.view.MenuItem;
16 | import android.view.View;
17 | import android.widget.Toast;
18 |
19 | import com.xiaopo.flying.sticker.BitmapStickerIcon;
20 | import com.xiaopo.flying.sticker.DeleteIconEvent;
21 | import com.xiaopo.flying.sticker.DrawableSticker;
22 | import com.xiaopo.flying.sticker.FlipHorizontallyEvent;
23 | import com.xiaopo.flying.sticker.Sticker;
24 | import com.xiaopo.flying.sticker.StickerView;
25 | import com.xiaopo.flying.sticker.TextSticker;
26 | import com.xiaopo.flying.sticker.ZoomIconEvent;
27 | import com.xiaopo.flying.stickerview.util.FileUtil;
28 |
29 | import java.io.File;
30 | import java.util.Arrays;
31 |
32 | public class MainActivity extends AppCompatActivity {
33 | private static final String TAG = MainActivity.class.getSimpleName();
34 | public static final int PERM_RQST_CODE = 110;
35 | private StickerView stickerView;
36 | private TextSticker sticker;
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_main);
42 |
43 | stickerView = (StickerView) findViewById(R.id.sticker_view);
44 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
45 |
46 | //currently you can config your own icons and icon event
47 | //the event you can custom
48 | BitmapStickerIcon deleteIcon = new BitmapStickerIcon(ContextCompat.getDrawable(this,
49 | com.xiaopo.flying.sticker.R.drawable.sticker_ic_close_white_18dp),
50 | BitmapStickerIcon.LEFT_TOP);
51 | deleteIcon.setIconEvent(new DeleteIconEvent());
52 |
53 | BitmapStickerIcon zoomIcon = new BitmapStickerIcon(ContextCompat.getDrawable(this,
54 | com.xiaopo.flying.sticker.R.drawable.sticker_ic_scale_white_18dp),
55 | BitmapStickerIcon.RIGHT_BOTOM);
56 | zoomIcon.setIconEvent(new ZoomIconEvent());
57 |
58 | BitmapStickerIcon flipIcon = new BitmapStickerIcon(ContextCompat.getDrawable(this,
59 | com.xiaopo.flying.sticker.R.drawable.sticker_ic_flip_white_18dp),
60 | BitmapStickerIcon.RIGHT_TOP);
61 | flipIcon.setIconEvent(new FlipHorizontallyEvent());
62 |
63 | BitmapStickerIcon heartIcon =
64 | new BitmapStickerIcon(ContextCompat.getDrawable(this, R.drawable.ic_favorite_white_24dp),
65 | BitmapStickerIcon.LEFT_BOTTOM);
66 | heartIcon.setIconEvent(new HelloIconEvent());
67 |
68 | stickerView.setIcons(Arrays.asList(deleteIcon, zoomIcon, flipIcon, heartIcon));
69 |
70 | //default icon layout
71 | //stickerView.configDefaultIcons();
72 |
73 | stickerView.setBackgroundColor(Color.WHITE);
74 | stickerView.setLocked(false);
75 | stickerView.setConstrained(true);
76 |
77 | sticker = new TextSticker(this);
78 |
79 | sticker.setDrawable(ContextCompat.getDrawable(getApplicationContext(),
80 | R.drawable.sticker_transparent_background));
81 | sticker.setText("Hello, world!");
82 | sticker.setTextColor(Color.BLACK);
83 | sticker.setTextAlign(Layout.Alignment.ALIGN_CENTER);
84 | sticker.resizeText();
85 |
86 | stickerView.setOnStickerOperationListener(new StickerView.OnStickerOperationListener() {
87 | @Override
88 | public void onStickerAdded(@NonNull Sticker sticker) {
89 | Log.d(TAG, "onStickerAdded");
90 | }
91 |
92 | @Override
93 | public void onStickerClicked(@NonNull Sticker sticker) {
94 | //stickerView.removeAllSticker();
95 | if (sticker instanceof TextSticker) {
96 | ((TextSticker) sticker).setTextColor(Color.RED);
97 | stickerView.replace(sticker);
98 | stickerView.invalidate();
99 | }
100 | Log.d(TAG, "onStickerClicked");
101 | }
102 |
103 | @Override
104 | public void onStickerDeleted(@NonNull Sticker sticker) {
105 | Log.d(TAG, "onStickerDeleted");
106 | }
107 |
108 | @Override
109 | public void onStickerDragFinished(@NonNull Sticker sticker) {
110 | Log.d(TAG, "onStickerDragFinished");
111 | }
112 |
113 | @Override
114 | public void onStickerTouchedDown(@NonNull Sticker sticker) {
115 | Log.d(TAG, "onStickerTouchedDown");
116 | }
117 |
118 | @Override
119 | public void onStickerZoomFinished(@NonNull Sticker sticker) {
120 | Log.d(TAG, "onStickerZoomFinished");
121 | }
122 |
123 | @Override
124 | public void onStickerFlipped(@NonNull Sticker sticker) {
125 | Log.d(TAG, "onStickerFlipped");
126 | }
127 |
128 | @Override
129 | public void onStickerDoubleTapped(@NonNull Sticker sticker) {
130 | Log.d(TAG, "onDoubleTapped: double tap will be with two click");
131 | }
132 | });
133 |
134 | if (toolbar != null) {
135 | toolbar.setTitle(R.string.app_name);
136 | toolbar.inflateMenu(R.menu.menu_save);
137 | toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
138 | @Override
139 | public boolean onMenuItemClick(MenuItem item) {
140 | if (item.getItemId() == R.id.item_save) {
141 | File file = FileUtil.getNewFile(MainActivity.this, "Sticker");
142 | if (file != null) {
143 | stickerView.save(file);
144 | Toast.makeText(MainActivity.this, "saved in " + file.getAbsolutePath(),
145 | Toast.LENGTH_SHORT).show();
146 | } else {
147 | Toast.makeText(MainActivity.this, "the file is null", Toast.LENGTH_SHORT).show();
148 | }
149 | }
150 | // stickerView.replace(new DrawableSticker(
151 | // ContextCompat.getDrawable(MainActivity.this, R.drawable.haizewang_90)
152 | // ));
153 | return false;
154 | }
155 | });
156 | }
157 |
158 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
159 | != PackageManager.PERMISSION_GRANTED
160 | || ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
161 | != PackageManager.PERMISSION_GRANTED) {
162 | ActivityCompat.requestPermissions(this,
163 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERM_RQST_CODE);
164 | } else {
165 | loadSticker();
166 | }
167 | }
168 |
169 | private void loadSticker() {
170 | Drawable drawable =
171 | ContextCompat.getDrawable(this, R.drawable.haizewang_215);
172 | Drawable drawable1 =
173 | ContextCompat.getDrawable(this, R.drawable.haizewang_23);
174 | stickerView.addSticker(new DrawableSticker(drawable));
175 | stickerView.addSticker(new DrawableSticker(drawable1), Sticker.Position.BOTTOM | Sticker.Position.RIGHT);
176 |
177 | Drawable bubble = ContextCompat.getDrawable(this, R.drawable.bubble);
178 | stickerView.addSticker(
179 | new TextSticker(getApplicationContext())
180 | .setDrawable(bubble)
181 | .setText("Sticker\n")
182 | .setMaxTextSize(14)
183 | .resizeText()
184 | , Sticker.Position.TOP);
185 | }
186 |
187 | @Override
188 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
189 | @NonNull int[] grantResults) {
190 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
191 | if (requestCode == PERM_RQST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
192 | loadSticker();
193 | }
194 | }
195 |
196 | public void testReplace(View view) {
197 | if (stickerView.replace(sticker)) {
198 | Toast.makeText(MainActivity.this, "Replace Sticker successfully!", Toast.LENGTH_SHORT).show();
199 | } else {
200 | Toast.makeText(MainActivity.this, "Replace Sticker failed!", Toast.LENGTH_SHORT).show();
201 | }
202 | }
203 |
204 | public void testLock(View view) {
205 | stickerView.setLocked(!stickerView.isLocked());
206 | }
207 |
208 | public void testRemove(View view) {
209 | if (stickerView.removeCurrentSticker()) {
210 | Toast.makeText(MainActivity.this, "Remove current Sticker successfully!", Toast.LENGTH_SHORT)
211 | .show();
212 | } else {
213 | Toast.makeText(MainActivity.this, "Remove current Sticker failed!", Toast.LENGTH_SHORT)
214 | .show();
215 | }
216 | }
217 |
218 | public void testRemoveAll(View view) {
219 | stickerView.removeAllStickers();
220 | }
221 |
222 | public void reset(View view) {
223 | stickerView.removeAllStickers();
224 | loadSticker();
225 | }
226 |
227 | public void testAdd(View view) {
228 | final TextSticker sticker = new TextSticker(this);
229 | sticker.setText("Hello, world!");
230 | sticker.setTextColor(Color.BLUE);
231 | sticker.setTextAlign(Layout.Alignment.ALIGN_CENTER);
232 | sticker.resizeText();
233 |
234 | stickerView.addSticker(sticker);
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xiaopo/flying/stickerview/util/FileUtil.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.stickerview.util;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import android.os.Environment;
7 | import android.provider.MediaStore;
8 | import android.text.TextUtils;
9 | import android.util.Log;
10 |
11 | import java.io.File;
12 | import java.io.FileInputStream;
13 | import java.io.FileNotFoundException;
14 | import java.io.FileOutputStream;
15 | import java.io.IOException;
16 | import java.nio.channels.FileChannel;
17 | import java.text.SimpleDateFormat;
18 | import java.util.Date;
19 | import java.util.Locale;
20 |
21 | /**
22 | * Created by snowbean on 16-8-5.
23 | */
24 | public class FileUtil {
25 | private static final String TAG = "FileUtil";
26 |
27 | public static String getFolderName(String name) {
28 | File mediaStorageDir =
29 | new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
30 | name);
31 |
32 | if (!mediaStorageDir.exists()) {
33 | if (!mediaStorageDir.mkdirs()) {
34 | return "";
35 | }
36 | }
37 | return mediaStorageDir.getAbsolutePath();
38 | }
39 |
40 | /**
41 | * 判断sd卡是否可以用
42 | */
43 | private static boolean isSDAvailable() {
44 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
45 | }
46 |
47 | public static File getNewFile(Context context, String folderName) {
48 |
49 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA);
50 |
51 | String timeStamp = simpleDateFormat.format(new Date());
52 |
53 | String path;
54 | if (isSDAvailable()) {
55 | path = getFolderName(folderName) + File.separator + timeStamp + ".jpg";
56 | } else {
57 | path = context.getFilesDir().getPath() + File.separator + timeStamp + ".jpg";
58 | }
59 |
60 | if (TextUtils.isEmpty(path)) {
61 | return null;
62 | }
63 |
64 | return new File(path);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_favorite_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/drawable-hdpi/ic_favorite_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_favorite_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/drawable-mdpi/ic_favorite_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/border.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/drawable-nodpi/border.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/bubble.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/drawable-nodpi/bubble.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/haizewang_215.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/drawable-nodpi/haizewang_215.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/haizewang_23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/drawable-nodpi/haizewang_23.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/haizewang_90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/drawable-nodpi/haizewang_90.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_favorite_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/drawable-xhdpi/ic_favorite_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_favorite_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/drawable-xxhdpi/ic_favorite_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_favorite_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/drawable-xxxhdpi/ic_favorite_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
20 |
21 |
30 |
31 |
36 |
37 |
43 |
44 |
45 |
46 |
52 |
57 |
64 |
65 |
72 |
73 |
80 |
81 |
88 |
89 |
96 |
97 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_save.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/black
4 | @android:color/black
5 | @android:color/white
6 |
7 | #ff666666
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | StickerView
3 | 空山新雨后,天气晚来秋。明月松间照,清泉石上流。竹喧归浣女,莲动下渔舟。随意春芳歇,王孙自可留。
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/xiaopo/flying/stickerview/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.stickerview;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test public void addition_isCorrect() throws Exception {
14 | assertEquals(4, 2 + 2);
15 | }
16 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.3'
9 |
10 | // classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'
11 | // classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | jcenter()
20 | maven { url "https://jitpack.io" }
21 | }
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
27 |
28 | ext {
29 | supportLibraryVersion = '25.3.1'
30 | photoViewVersion = 'v1.2.5'
31 | }
32 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## Project-wide Gradle settings.
2 | #
3 | # For more details on how to configure your build environment visit
4 | # http://www.gradle.org/docs/current/userguide/build_environment.html
5 | #
6 | # Specifies the JVM arguments used for the daemon process.
7 | # The setting is particularly useful for tweaking memory settings.
8 | # Default value: -Xmx1024m -XX:MaxPermSize=256m
9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
10 | #
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | #Thu Jan 26 19:36:53 CST 2017
16 | #systemProp.http.proxyHost=127.0.0.1
17 | org.gradle.jvmargs=-Xmx1536m
18 | #systemProp.http.proxyPort=1080
19 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Feb 06 22:09:03 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/screenshots/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/screenshots/screenshot1.png
--------------------------------------------------------------------------------
/screenshots/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/screenshots/screenshot2.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':sticker'
2 |
--------------------------------------------------------------------------------
/sticker/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sticker/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 9
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 |
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 |
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | compile fileTree(dir: 'libs', include: ['*.jar'])
26 | compile "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion"
27 | }
28 |
29 | //apply plugin: 'com.android.library'
30 | //apply plugin: 'com.github.dcendents.android-maven'
31 | //apply plugin: 'com.jfrog.bintray'
32 | //
33 | //version = "1.6.0" // #修改# // 这里是aar的版本号
34 | //
35 | //android {
36 | // compileSdkVersion 25
37 | // buildToolsVersion "25.0.2"
38 | // resourcePrefix 'stickerview_'
39 | //
40 | // defaultConfig {
41 | // minSdkVersion 9
42 | // targetSdkVersion 25
43 | // versionCode 1
44 | // versionName "1.0"
45 | //
46 | // testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
47 | // }
48 | // buildTypes {
49 | // release {
50 | // minifyEnabled false
51 | // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
52 | // }
53 | // }
54 | //
55 | // lintOptions {
56 | // abortOnError false
57 | // }
58 | //}
59 | //
60 | //dependencies {
61 | // compile fileTree(include: ['*.jar'], dir: 'libs')
62 | // compile "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion"
63 | //}
64 | //
65 | //def siteUrl = 'https://github.com/wuapnjie/StickerView'
66 | //// #修改# // 项目的主页地址,我这里是我的PickerView项目在github的链接地址
67 | //def gitUrl = 'https://github.com/wuapnjie/StickerView.git'
68 | //// #修改# // 项目 git 地址,我这里同样是用Github上的git地址
69 | //group =
70 | // "com.flying.xiaopo" // #修改# // 组名称,这个相当于依赖的时候 compile 'com.bigkoo:pickerview:1.0.1' “:”号前面的前缀
71 | //
72 | //install {
73 | // repositories.mavenInstaller {
74 | // // This generates POM.xml with proper parameters
75 | // pom {
76 | // project {
77 | // packaging 'aar'
78 | // name 'Sticker support For Android' // #修改# // 标题
79 | // url siteUrl
80 | // // Set your license
81 | // licenses {
82 | // license {
83 | // name 'The Apache Software License, Version 2.0'
84 | // url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
85 | // }
86 | // }
87 | // developers {
88 | // developer {
89 | // id 'wuapnjie'
90 | // // #修改# // 你的userid,昵称
91 | // name 'wupanjie' // #修改# // 用户名
92 | // email 'wupanjie0611@gmail.com' // #修改# // 邮箱
93 | // }
94 | // }
95 | // scm {
96 | // connection gitUrl
97 | // developerConnection gitUrl
98 | // url siteUrl
99 | // }
100 | // }
101 | // }
102 | // }
103 | //}
104 | //
105 | //task sourcesJar(type: Jar) {
106 | // from android.sourceSets.main.java.srcDirs
107 | // classifier = 'sources'
108 | //}
109 | //
110 | //task javadoc(type: Javadoc) {
111 | // source = android.sourceSets.main.java.srcDirs
112 | // classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
113 | //}
114 | //
115 | //task javadocJar(type: Jar, dependsOn: javadoc) {
116 | // classifier = 'javadoc'
117 | // from javadoc.destinationDir
118 | //}
119 | //
120 | //artifacts {
121 | // archives javadocJar
122 | // archives sourcesJar
123 | //}
124 | //
125 | //Properties properties = new Properties()
126 | //properties.load(project.rootProject.file('local.properties').newDataInputStream())
127 | //bintray {
128 | // user = properties.getProperty("bintray.user")
129 | // key = properties.getProperty("bintray.apikey")
130 | // // 上面两个 user和key 需要留意一下,在local.properites 里面配置的
131 | // configurations = ['archives']
132 | // pkg {
133 | // repo = "maven"
134 | // name = "stickerview"
135 | // // #修改# // 在 jcenter 上面的项目名字
136 | // websiteUrl = siteUrl
137 | // vcsUrl = gitUrl
138 | // licenses = ["Apache-2.0"]
139 | // publish = true
140 | // }
141 | //}
142 |
143 | tasks.withType(Javadoc) {
144 | options.addStringOption('Xdoclint:none', '-quiet')
145 | options.addStringOption('encoding', 'UTF-8')
146 | options.addStringOption('charSet', 'UTF-8')
147 | }
148 |
149 |
--------------------------------------------------------------------------------
/sticker/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 /usr/local/android-sdk-linux/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 |
--------------------------------------------------------------------------------
/sticker/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/AbstractFlipEvent.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | import android.view.MotionEvent;
4 |
5 | public abstract class AbstractFlipEvent implements StickerIconEvent {
6 |
7 | @Override public void onActionDown(StickerView stickerView, MotionEvent event) {
8 |
9 | }
10 |
11 | @Override public void onActionMove(StickerView stickerView, MotionEvent event) {
12 |
13 | }
14 |
15 | @Override public void onActionUp(StickerView stickerView, MotionEvent event) {
16 | stickerView.flipCurrentSticker(getFlipDirection());
17 | }
18 |
19 | @StickerView.Flip protected abstract int getFlipDirection();
20 | }
21 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/BitmapStickerIcon.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Paint;
5 | import android.graphics.drawable.Drawable;
6 | import android.support.annotation.IntDef;
7 | import android.view.MotionEvent;
8 | import java.lang.annotation.Retention;
9 | import java.lang.annotation.RetentionPolicy;
10 |
11 | /**
12 | * @author wupanjie
13 | */
14 | public class BitmapStickerIcon extends DrawableSticker implements StickerIconEvent {
15 | public static final float DEFAULT_ICON_RADIUS = 30f;
16 | public static final float DEFAULT_ICON_EXTRA_RADIUS = 10f;
17 |
18 | @IntDef({ LEFT_TOP, RIGHT_TOP, LEFT_BOTTOM, RIGHT_BOTOM }) @Retention(RetentionPolicy.SOURCE)
19 | public @interface Gravity {
20 |
21 | }
22 |
23 | public static final int LEFT_TOP = 0;
24 | public static final int RIGHT_TOP = 1;
25 | public static final int LEFT_BOTTOM = 2;
26 | public static final int RIGHT_BOTOM = 3;
27 |
28 | private float iconRadius = DEFAULT_ICON_RADIUS;
29 | private float iconExtraRadius = DEFAULT_ICON_EXTRA_RADIUS;
30 | private float x;
31 | private float y;
32 | @Gravity private int position = LEFT_TOP;
33 |
34 | private StickerIconEvent iconEvent;
35 |
36 | public BitmapStickerIcon(Drawable drawable, @Gravity int gravity) {
37 | super(drawable);
38 | this.position = gravity;
39 | }
40 |
41 | public void draw(Canvas canvas, Paint paint) {
42 | canvas.drawCircle(x, y, iconRadius, paint);
43 | super.draw(canvas);
44 | }
45 |
46 | public float getX() {
47 | return x;
48 | }
49 |
50 | public void setX(float x) {
51 | this.x = x;
52 | }
53 |
54 | public float getY() {
55 | return y;
56 | }
57 |
58 | public void setY(float y) {
59 | this.y = y;
60 | }
61 |
62 | public float getIconRadius() {
63 | return iconRadius;
64 | }
65 |
66 | public void setIconRadius(float iconRadius) {
67 | this.iconRadius = iconRadius;
68 | }
69 |
70 | public float getIconExtraRadius() {
71 | return iconExtraRadius;
72 | }
73 |
74 | public void setIconExtraRadius(float iconExtraRadius) {
75 | this.iconExtraRadius = iconExtraRadius;
76 | }
77 |
78 | @Override public void onActionDown(StickerView stickerView, MotionEvent event) {
79 | if (iconEvent != null) {
80 | iconEvent.onActionDown(stickerView, event);
81 | }
82 | }
83 |
84 | @Override public void onActionMove(StickerView stickerView, MotionEvent event) {
85 | if (iconEvent != null) {
86 | iconEvent.onActionMove(stickerView, event);
87 | }
88 | }
89 |
90 | @Override public void onActionUp(StickerView stickerView, MotionEvent event) {
91 | if (iconEvent != null) {
92 | iconEvent.onActionUp(stickerView, event);
93 | }
94 | }
95 |
96 | public StickerIconEvent getIconEvent() {
97 | return iconEvent;
98 | }
99 |
100 | public void setIconEvent(StickerIconEvent iconEvent) {
101 | this.iconEvent = iconEvent;
102 | }
103 |
104 | @Gravity public int getPosition() {
105 | return position;
106 | }
107 |
108 | public void setPosition(@Gravity int position) {
109 | this.position = position;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/DeleteIconEvent.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | import android.view.MotionEvent;
4 |
5 | /**
6 | * @author wupanjie
7 | */
8 |
9 | public class DeleteIconEvent implements StickerIconEvent {
10 | @Override public void onActionDown(StickerView stickerView, MotionEvent event) {
11 |
12 | }
13 |
14 | @Override public void onActionMove(StickerView stickerView, MotionEvent event) {
15 |
16 | }
17 |
18 | @Override public void onActionUp(StickerView stickerView, MotionEvent event) {
19 | stickerView.removeCurrentSticker();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/DrawableSticker.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Rect;
5 | import android.graphics.drawable.Drawable;
6 | import android.support.annotation.IntRange;
7 | import android.support.annotation.NonNull;
8 |
9 | /**
10 | * @author wupanjie
11 | */
12 | public class DrawableSticker extends Sticker {
13 |
14 | private Drawable drawable;
15 | private Rect realBounds;
16 |
17 | public DrawableSticker(Drawable drawable) {
18 | this.drawable = drawable;
19 | realBounds = new Rect(0, 0, getWidth(), getHeight());
20 | }
21 |
22 | @NonNull @Override public Drawable getDrawable() {
23 | return drawable;
24 | }
25 |
26 | @Override public DrawableSticker setDrawable(@NonNull Drawable drawable) {
27 | this.drawable = drawable;
28 | return this;
29 | }
30 |
31 | @Override public void draw(@NonNull Canvas canvas) {
32 | canvas.save();
33 | canvas.concat(getMatrix());
34 | drawable.setBounds(realBounds);
35 | drawable.draw(canvas);
36 | canvas.restore();
37 | }
38 |
39 | @NonNull @Override public DrawableSticker setAlpha(@IntRange(from = 0, to = 255) int alpha) {
40 | drawable.setAlpha(alpha);
41 | return this;
42 | }
43 |
44 | @Override public int getWidth() {
45 | return drawable.getIntrinsicWidth();
46 | }
47 |
48 | @Override public int getHeight() {
49 | return drawable.getIntrinsicHeight();
50 | }
51 |
52 | @Override public void release() {
53 | super.release();
54 | if (drawable != null) {
55 | drawable = null;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/FlipBothDirectionsEvent.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | /**
4 | * @author wupanjie
5 | */
6 |
7 | public class FlipBothDirectionsEvent extends AbstractFlipEvent {
8 |
9 | @Override @StickerView.Flip protected int getFlipDirection() {
10 | return StickerView.FLIP_VERTICALLY | StickerView.FLIP_HORIZONTALLY;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/FlipHorizontallyEvent.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | /**
4 | * @author wupanjie
5 | */
6 |
7 | public class FlipHorizontallyEvent extends AbstractFlipEvent {
8 |
9 | @Override @StickerView.Flip protected int getFlipDirection() {
10 | return StickerView.FLIP_HORIZONTALLY;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/FlipVerticallyEvent.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | /**
4 | * @author wupanjie
5 | */
6 |
7 | public class FlipVerticallyEvent extends AbstractFlipEvent {
8 |
9 | @Override @StickerView.Flip protected int getFlipDirection() {
10 | return StickerView.FLIP_VERTICALLY;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/Sticker.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Matrix;
5 | import android.graphics.PointF;
6 | import android.graphics.RectF;
7 | import android.graphics.drawable.Drawable;
8 | import android.support.annotation.IntDef;
9 | import android.support.annotation.IntRange;
10 | import android.support.annotation.NonNull;
11 | import android.support.annotation.Nullable;
12 | import java.lang.annotation.Retention;
13 | import java.lang.annotation.RetentionPolicy;
14 |
15 | /**
16 | * @author wupanjie
17 | */
18 | public abstract class Sticker {
19 |
20 | @IntDef(flag = true, value = {
21 | Position.CENTER, Position.TOP, Position.BOTTOM, Position.LEFT, Position.RIGHT
22 | }) @Retention(RetentionPolicy.SOURCE) public @interface Position {
23 | int CENTER = 1;
24 | int TOP = 1 << 1;
25 | int LEFT = 1 << 2;
26 | int RIGHT = 1 << 3;
27 | int BOTTOM = 1 << 4;
28 | }
29 |
30 | private final float[] matrixValues = new float[9];
31 | private final float[] unrotatedWrapperCorner = new float[8];
32 | private final float[] unrotatedPoint = new float[2];
33 | private final float[] boundPoints = new float[8];
34 | private final float[] mappedBounds = new float[8];
35 | private final RectF trappedRect = new RectF();
36 | private final Matrix matrix = new Matrix();
37 | private boolean isFlippedHorizontally;
38 | private boolean isFlippedVertically;
39 |
40 | public boolean isFlippedHorizontally() {
41 | return isFlippedHorizontally;
42 | }
43 |
44 | @NonNull public Sticker setFlippedHorizontally(boolean flippedHorizontally) {
45 | isFlippedHorizontally = flippedHorizontally;
46 | return this;
47 | }
48 |
49 | public boolean isFlippedVertically() {
50 | return isFlippedVertically;
51 | }
52 |
53 | @NonNull public Sticker setFlippedVertically(boolean flippedVertically) {
54 | isFlippedVertically = flippedVertically;
55 | return this;
56 | }
57 |
58 | @NonNull public Matrix getMatrix() {
59 | return matrix;
60 | }
61 |
62 | public Sticker setMatrix(@Nullable Matrix matrix) {
63 | this.matrix.set(matrix);
64 | return this;
65 | }
66 |
67 | public abstract void draw(@NonNull Canvas canvas);
68 |
69 | public abstract int getWidth();
70 |
71 | public abstract int getHeight();
72 |
73 | public abstract Sticker setDrawable(@NonNull Drawable drawable);
74 |
75 | @NonNull public abstract Drawable getDrawable();
76 |
77 | @NonNull public abstract Sticker setAlpha(@IntRange(from = 0, to = 255) int alpha);
78 |
79 | public float[] getBoundPoints() {
80 | float[] points = new float[8];
81 | getBoundPoints(points);
82 | return points;
83 | }
84 |
85 | public void getBoundPoints(@NonNull float[] points) {
86 | if (!isFlippedHorizontally) {
87 | if (!isFlippedVertically) {
88 | points[0] = 0f;
89 | points[1] = 0f;
90 | points[2] = getWidth();
91 | points[3] = 0f;
92 | points[4] = 0f;
93 | points[5] = getHeight();
94 | points[6] = getWidth();
95 | points[7] = getHeight();
96 | } else {
97 | points[0] = 0f;
98 | points[1] = getHeight();
99 | points[2] = getWidth();
100 | points[3] = getHeight();
101 | points[4] = 0f;
102 | points[5] = 0f;
103 | points[6] = getWidth();
104 | points[7] = 0f;
105 | }
106 | } else {
107 | if (!isFlippedVertically) {
108 | points[0] = getWidth();
109 | points[1] = 0f;
110 | points[2] = 0f;
111 | points[3] = 0f;
112 | points[4] = getWidth();
113 | points[5] = getHeight();
114 | points[6] = 0f;
115 | points[7] = getHeight();
116 | } else {
117 | points[0] = getWidth();
118 | points[1] = getHeight();
119 | points[2] = 0f;
120 | points[3] = getHeight();
121 | points[4] = getWidth();
122 | points[5] = 0f;
123 | points[6] = 0f;
124 | points[7] = 0f;
125 | }
126 | }
127 | }
128 |
129 | @NonNull public float[] getMappedBoundPoints() {
130 | float[] dst = new float[8];
131 | getMappedPoints(dst, getBoundPoints());
132 | return dst;
133 | }
134 |
135 | @NonNull public float[] getMappedPoints(@NonNull float[] src) {
136 | float[] dst = new float[src.length];
137 | matrix.mapPoints(dst, src);
138 | return dst;
139 | }
140 |
141 | public void getMappedPoints(@NonNull float[] dst, @NonNull float[] src) {
142 | matrix.mapPoints(dst, src);
143 | }
144 |
145 | @NonNull public RectF getBound() {
146 | RectF bound = new RectF();
147 | getBound(bound);
148 | return bound;
149 | }
150 |
151 | public void getBound(@NonNull RectF dst) {
152 | dst.set(0, 0, getWidth(), getHeight());
153 | }
154 |
155 | @NonNull public RectF getMappedBound() {
156 | RectF dst = new RectF();
157 | getMappedBound(dst, getBound());
158 | return dst;
159 | }
160 |
161 | public void getMappedBound(@NonNull RectF dst, @NonNull RectF bound) {
162 | matrix.mapRect(dst, bound);
163 | }
164 |
165 | @NonNull public PointF getCenterPoint() {
166 | PointF center = new PointF();
167 | getCenterPoint(center);
168 | return center;
169 | }
170 |
171 | public void getCenterPoint(@NonNull PointF dst) {
172 | dst.set(getWidth() * 1f / 2, getHeight() * 1f / 2);
173 | }
174 |
175 | @NonNull public PointF getMappedCenterPoint() {
176 | PointF pointF = getCenterPoint();
177 | getMappedCenterPoint(pointF, new float[2], new float[2]);
178 | return pointF;
179 | }
180 |
181 | public void getMappedCenterPoint(@NonNull PointF dst, @NonNull float[] mappedPoints,
182 | @NonNull float[] src) {
183 | getCenterPoint(dst);
184 | src[0] = dst.x;
185 | src[1] = dst.y;
186 | getMappedPoints(mappedPoints, src);
187 | dst.set(mappedPoints[0], mappedPoints[1]);
188 | }
189 |
190 | public float getCurrentScale() {
191 | return getMatrixScale(matrix);
192 | }
193 |
194 | public float getCurrentHeight() {
195 | return getMatrixScale(matrix) * getHeight();
196 | }
197 |
198 | public float getCurrentWidth() {
199 | return getMatrixScale(matrix) * getWidth();
200 | }
201 |
202 | /**
203 | * This method calculates scale value for given Matrix object.
204 | */
205 | public float getMatrixScale(@NonNull Matrix matrix) {
206 | return (float) Math.sqrt(Math.pow(getMatrixValue(matrix, Matrix.MSCALE_X), 2) + Math.pow(
207 | getMatrixValue(matrix, Matrix.MSKEW_Y), 2));
208 | }
209 |
210 | /**
211 | * @return - current image rotation angle.
212 | */
213 | public float getCurrentAngle() {
214 | return getMatrixAngle(matrix);
215 | }
216 |
217 | /**
218 | * This method calculates rotation angle for given Matrix object.
219 | */
220 | public float getMatrixAngle(@NonNull Matrix matrix) {
221 | return (float) Math.toDegrees(-(Math.atan2(getMatrixValue(matrix, Matrix.MSKEW_X),
222 | getMatrixValue(matrix, Matrix.MSCALE_X))));
223 | }
224 |
225 | public float getMatrixValue(@NonNull Matrix matrix, @IntRange(from = 0, to = 9) int valueIndex) {
226 | matrix.getValues(matrixValues);
227 | return matrixValues[valueIndex];
228 | }
229 |
230 | public boolean contains(float x, float y) {
231 | return contains(new float[] { x, y });
232 | }
233 |
234 | public boolean contains(@NonNull float[] point) {
235 | Matrix tempMatrix = new Matrix();
236 | tempMatrix.setRotate(-getCurrentAngle());
237 | getBoundPoints(boundPoints);
238 | getMappedPoints(mappedBounds, boundPoints);
239 | tempMatrix.mapPoints(unrotatedWrapperCorner, mappedBounds);
240 | tempMatrix.mapPoints(unrotatedPoint, point);
241 | StickerUtils.trapToRect(trappedRect, unrotatedWrapperCorner);
242 | return trappedRect.contains(unrotatedPoint[0], unrotatedPoint[1]);
243 | }
244 |
245 | public void release() {
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/StickerIconEvent.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | import android.view.MotionEvent;
4 |
5 | /**
6 | * @author wupanjie
7 | */
8 |
9 | public interface StickerIconEvent {
10 | void onActionDown(StickerView stickerView, MotionEvent event);
11 |
12 | void onActionMove(StickerView stickerView, MotionEvent event);
13 |
14 | void onActionUp(StickerView stickerView, MotionEvent event);
15 | }
16 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/StickerUtils.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.graphics.Bitmap;
6 | import android.graphics.RectF;
7 | import android.net.Uri;
8 | import android.provider.MediaStore;
9 | import android.support.annotation.NonNull;
10 | import android.util.Log;
11 | import java.io.File;
12 | import java.io.FileNotFoundException;
13 | import java.io.FileOutputStream;
14 | import java.io.IOException;
15 |
16 | import static java.lang.Math.round;
17 |
18 | /**
19 | * @author wupanjie
20 | */
21 | class StickerUtils {
22 | private static final String TAG = "StickerView";
23 |
24 | public static File saveImageToGallery(@NonNull File file, @NonNull Bitmap bmp) {
25 | if (bmp == null) {
26 | throw new IllegalArgumentException("bmp should not be null");
27 | }
28 | try {
29 | FileOutputStream fos = new FileOutputStream(file);
30 | bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
31 | fos.flush();
32 | fos.close();
33 | } catch (IOException e) {
34 | e.printStackTrace();
35 | }
36 |
37 | Log.e(TAG, "saveImageToGallery: the path of bmp is " + file.getAbsolutePath());
38 | return file;
39 | }
40 |
41 | public static void notifySystemGallery(@NonNull Context context, @NonNull File file) {
42 | if (file == null || !file.exists()) {
43 | throw new IllegalArgumentException("bmp should not be null");
44 | }
45 |
46 | try {
47 | MediaStore.Images.Media.insertImage(context.getContentResolver(), file.getAbsolutePath(),
48 | file.getName(), null);
49 | } catch (FileNotFoundException e) {
50 | throw new IllegalStateException("File couldn't be found");
51 | }
52 | context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
53 | }
54 |
55 | @NonNull public static RectF trapToRect(@NonNull float[] array) {
56 | RectF r = new RectF();
57 | trapToRect(r, array);
58 | return r;
59 | }
60 |
61 | public static void trapToRect(@NonNull RectF r, @NonNull float[] array) {
62 | r.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY,
63 | Float.NEGATIVE_INFINITY);
64 | for (int i = 1; i < array.length; i += 2) {
65 | float x = round(array[i - 1] * 10) / 10.f;
66 | float y = round(array[i] * 10) / 10.f;
67 | r.left = (x < r.left) ? x : r.left;
68 | r.top = (y < r.top) ? y : r.top;
69 | r.right = (x > r.right) ? x : r.right;
70 | r.bottom = (y > r.bottom) ? y : r.bottom;
71 | }
72 | r.sort();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/StickerView.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Matrix;
9 | import android.graphics.Paint;
10 | import android.graphics.PointF;
11 | import android.graphics.RectF;
12 | import android.os.SystemClock;
13 | import android.support.annotation.IntDef;
14 | import android.support.annotation.NonNull;
15 | import android.support.annotation.Nullable;
16 | import android.support.v4.content.ContextCompat;
17 | import android.support.v4.view.MotionEventCompat;
18 | import android.support.v4.view.ViewCompat;
19 | import android.util.AttributeSet;
20 | import android.util.Log;
21 | import android.view.MotionEvent;
22 | import android.view.ViewConfiguration;
23 | import android.widget.FrameLayout;
24 | import java.io.File;
25 | import java.lang.annotation.Retention;
26 | import java.lang.annotation.RetentionPolicy;
27 | import java.util.ArrayList;
28 | import java.util.Arrays;
29 | import java.util.Collections;
30 | import java.util.List;
31 |
32 | /**
33 | * Sticker View
34 | * @author wupanjie
35 | */
36 | public class StickerView extends FrameLayout {
37 |
38 | private final boolean showIcons;
39 | private final boolean showBorder;
40 | private final boolean bringToFrontCurrentSticker;
41 |
42 | @IntDef({
43 | ActionMode.NONE, ActionMode.DRAG, ActionMode.ZOOM_WITH_TWO_FINGER, ActionMode.ICON,
44 | ActionMode.CLICK
45 | }) @Retention(RetentionPolicy.SOURCE) protected @interface ActionMode {
46 | int NONE = 0;
47 | int DRAG = 1;
48 | int ZOOM_WITH_TWO_FINGER = 2;
49 | int ICON = 3;
50 | int CLICK = 4;
51 | }
52 |
53 | @IntDef(flag = true, value = { FLIP_HORIZONTALLY, FLIP_VERTICALLY })
54 | @Retention(RetentionPolicy.SOURCE) protected @interface Flip {
55 | }
56 |
57 | private static final String TAG = "StickerView";
58 |
59 | private static final int DEFAULT_MIN_CLICK_DELAY_TIME = 200;
60 |
61 | public static final int FLIP_HORIZONTALLY = 1;
62 | public static final int FLIP_VERTICALLY = 1 << 1;
63 |
64 | private final List stickers = new ArrayList<>();
65 | private final List icons = new ArrayList<>(4);
66 |
67 | private final Paint borderPaint = new Paint();
68 | private final RectF stickerRect = new RectF();
69 |
70 | private final Matrix sizeMatrix = new Matrix();
71 | private final Matrix downMatrix = new Matrix();
72 | private final Matrix moveMatrix = new Matrix();
73 |
74 | // region storing variables
75 | private final float[] bitmapPoints = new float[8];
76 | private final float[] bounds = new float[8];
77 | private final float[] point = new float[2];
78 | private final PointF currentCenterPoint = new PointF();
79 | private final float[] tmp = new float[2];
80 | private PointF midPoint = new PointF();
81 | // endregion
82 | private final int touchSlop;
83 |
84 | private BitmapStickerIcon currentIcon;
85 | //the first point down position
86 | private float downX;
87 | private float downY;
88 |
89 | private float oldDistance = 0f;
90 | private float oldRotation = 0f;
91 |
92 | @ActionMode private int currentMode = ActionMode.NONE;
93 |
94 | private Sticker handlingSticker;
95 |
96 | private boolean locked;
97 | private boolean constrained;
98 |
99 | private OnStickerOperationListener onStickerOperationListener;
100 |
101 | private long lastClickTime = 0;
102 | private int minClickDelayTime = DEFAULT_MIN_CLICK_DELAY_TIME;
103 |
104 | public StickerView(Context context) {
105 | this(context, null);
106 | }
107 |
108 | public StickerView(Context context, AttributeSet attrs) {
109 | this(context, attrs, 0);
110 | }
111 |
112 | public StickerView(Context context, AttributeSet attrs, int defStyleAttr) {
113 | super(context, attrs, defStyleAttr);
114 | touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
115 | TypedArray a = null;
116 | try {
117 | a = context.obtainStyledAttributes(attrs, R.styleable.StickerView);
118 | showIcons = a.getBoolean(R.styleable.StickerView_showIcons, false);
119 | showBorder = a.getBoolean(R.styleable.StickerView_showBorder, false);
120 | bringToFrontCurrentSticker =
121 | a.getBoolean(R.styleable.StickerView_bringToFrontCurrentSticker, false);
122 |
123 | borderPaint.setAntiAlias(true);
124 | borderPaint.setColor(a.getColor(R.styleable.StickerView_borderColor, Color.BLACK));
125 | borderPaint.setAlpha(a.getInteger(R.styleable.StickerView_borderAlpha, 128));
126 |
127 | configDefaultIcons();
128 | } finally {
129 | if (a != null) {
130 | a.recycle();
131 | }
132 | }
133 | }
134 |
135 | public void configDefaultIcons() {
136 | BitmapStickerIcon deleteIcon = new BitmapStickerIcon(
137 | ContextCompat.getDrawable(getContext(), R.drawable.sticker_ic_close_white_18dp),
138 | BitmapStickerIcon.LEFT_TOP);
139 | deleteIcon.setIconEvent(new DeleteIconEvent());
140 | BitmapStickerIcon zoomIcon = new BitmapStickerIcon(
141 | ContextCompat.getDrawable(getContext(), R.drawable.sticker_ic_scale_white_18dp),
142 | BitmapStickerIcon.RIGHT_BOTOM);
143 | zoomIcon.setIconEvent(new ZoomIconEvent());
144 | BitmapStickerIcon flipIcon = new BitmapStickerIcon(
145 | ContextCompat.getDrawable(getContext(), R.drawable.sticker_ic_flip_white_18dp),
146 | BitmapStickerIcon.RIGHT_TOP);
147 | flipIcon.setIconEvent(new FlipHorizontallyEvent());
148 |
149 | icons.clear();
150 | icons.add(deleteIcon);
151 | icons.add(zoomIcon);
152 | icons.add(flipIcon);
153 | }
154 |
155 | /**
156 | * Swaps sticker at layer [[oldPos]] with the one at layer [[newPos]].
157 | * Does nothing if either of the specified layers doesn't exist.
158 | */
159 | public void swapLayers(int oldPos, int newPos) {
160 | if (stickers.size() >= oldPos && stickers.size() >= newPos) {
161 | Collections.swap(stickers, oldPos, newPos);
162 | invalidate();
163 | }
164 | }
165 |
166 | /**
167 | * Sends sticker from layer [[oldPos]] to layer [[newPos]].
168 | * Does nothing if either of the specified layers doesn't exist.
169 | */
170 | public void sendToLayer(int oldPos, int newPos) {
171 | if (stickers.size() >= oldPos && stickers.size() >= newPos) {
172 | Sticker s = stickers.get(oldPos);
173 | stickers.remove(oldPos);
174 | stickers.add(newPos, s);
175 | invalidate();
176 | }
177 | }
178 |
179 | @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
180 | super.onLayout(changed, left, top, right, bottom);
181 | if (changed) {
182 | stickerRect.left = left;
183 | stickerRect.top = top;
184 | stickerRect.right = right;
185 | stickerRect.bottom = bottom;
186 | }
187 | }
188 |
189 | @Override protected void dispatchDraw(Canvas canvas) {
190 | super.dispatchDraw(canvas);
191 | drawStickers(canvas);
192 | }
193 |
194 | protected void drawStickers(Canvas canvas) {
195 | for (int i = 0; i < stickers.size(); i++) {
196 | Sticker sticker = stickers.get(i);
197 | if (sticker != null) {
198 | sticker.draw(canvas);
199 | }
200 | }
201 |
202 | if (handlingSticker != null && !locked && (showBorder || showIcons)) {
203 |
204 | getStickerPoints(handlingSticker, bitmapPoints);
205 |
206 | float x1 = bitmapPoints[0];
207 | float y1 = bitmapPoints[1];
208 | float x2 = bitmapPoints[2];
209 | float y2 = bitmapPoints[3];
210 | float x3 = bitmapPoints[4];
211 | float y3 = bitmapPoints[5];
212 | float x4 = bitmapPoints[6];
213 | float y4 = bitmapPoints[7];
214 |
215 | if (showBorder) {
216 | canvas.drawLine(x1, y1, x2, y2, borderPaint);
217 | canvas.drawLine(x1, y1, x3, y3, borderPaint);
218 | canvas.drawLine(x2, y2, x4, y4, borderPaint);
219 | canvas.drawLine(x4, y4, x3, y3, borderPaint);
220 | }
221 |
222 | //draw icons
223 | if (showIcons) {
224 | float rotation = calculateRotation(x4, y4, x3, y3);
225 | for (int i = 0; i < icons.size(); i++) {
226 | BitmapStickerIcon icon = icons.get(i);
227 | switch (icon.getPosition()) {
228 | case BitmapStickerIcon.LEFT_TOP:
229 |
230 | configIconMatrix(icon, x1, y1, rotation);
231 | break;
232 |
233 | case BitmapStickerIcon.RIGHT_TOP:
234 | configIconMatrix(icon, x2, y2, rotation);
235 | break;
236 |
237 | case BitmapStickerIcon.LEFT_BOTTOM:
238 | configIconMatrix(icon, x3, y3, rotation);
239 | break;
240 |
241 | case BitmapStickerIcon.RIGHT_BOTOM:
242 | configIconMatrix(icon, x4, y4, rotation);
243 | break;
244 | }
245 | icon.draw(canvas, borderPaint);
246 | }
247 | }
248 | }
249 | }
250 |
251 | protected void configIconMatrix(@NonNull BitmapStickerIcon icon, float x, float y,
252 | float rotation) {
253 | icon.setX(x);
254 | icon.setY(y);
255 | icon.getMatrix().reset();
256 |
257 | icon.getMatrix().postRotate(rotation, icon.getWidth() / 2, icon.getHeight() / 2);
258 | icon.getMatrix().postTranslate(x - icon.getWidth() / 2, y - icon.getHeight() / 2);
259 | }
260 |
261 | @Override public boolean onInterceptTouchEvent(MotionEvent ev) {
262 | if (locked) return super.onInterceptTouchEvent(ev);
263 |
264 | switch (ev.getAction()) {
265 | case MotionEvent.ACTION_DOWN:
266 | downX = ev.getX();
267 | downY = ev.getY();
268 |
269 | return findCurrentIconTouched() != null || findHandlingSticker() != null;
270 | }
271 |
272 | return super.onInterceptTouchEvent(ev);
273 | }
274 |
275 | @Override public boolean onTouchEvent(MotionEvent event) {
276 | if (locked) {
277 | return super.onTouchEvent(event);
278 | }
279 |
280 | int action = MotionEventCompat.getActionMasked(event);
281 |
282 | switch (action) {
283 | case MotionEvent.ACTION_DOWN:
284 | if (!onTouchDown(event)) {
285 | return false;
286 | }
287 | break;
288 | case MotionEvent.ACTION_POINTER_DOWN:
289 | oldDistance = calculateDistance(event);
290 | oldRotation = calculateRotation(event);
291 |
292 | midPoint = calculateMidPoint(event);
293 |
294 | if (handlingSticker != null && isInStickerArea(handlingSticker, event.getX(1),
295 | event.getY(1)) && findCurrentIconTouched() == null) {
296 | currentMode = ActionMode.ZOOM_WITH_TWO_FINGER;
297 | }
298 | break;
299 |
300 | case MotionEvent.ACTION_MOVE:
301 | handleCurrentMode(event);
302 | invalidate();
303 | break;
304 |
305 | case MotionEvent.ACTION_UP:
306 | onTouchUp(event);
307 | break;
308 |
309 | case MotionEvent.ACTION_POINTER_UP:
310 | if (currentMode == ActionMode.ZOOM_WITH_TWO_FINGER && handlingSticker != null) {
311 | if (onStickerOperationListener != null) {
312 | onStickerOperationListener.onStickerZoomFinished(handlingSticker);
313 | }
314 | }
315 | currentMode = ActionMode.NONE;
316 | break;
317 | }
318 |
319 | return true;
320 | }
321 |
322 | /**
323 | * @param event MotionEvent received from {@link #onTouchEvent)
324 | * @return true if has touch something
325 | */
326 | protected boolean onTouchDown(@NonNull MotionEvent event) {
327 | currentMode = ActionMode.DRAG;
328 |
329 | downX = event.getX();
330 | downY = event.getY();
331 |
332 | midPoint = calculateMidPoint();
333 | oldDistance = calculateDistance(midPoint.x, midPoint.y, downX, downY);
334 | oldRotation = calculateRotation(midPoint.x, midPoint.y, downX, downY);
335 |
336 | currentIcon = findCurrentIconTouched();
337 | if (currentIcon != null) {
338 | currentMode = ActionMode.ICON;
339 | currentIcon.onActionDown(this, event);
340 | } else {
341 | handlingSticker = findHandlingSticker();
342 | }
343 |
344 | if (handlingSticker != null) {
345 | downMatrix.set(handlingSticker.getMatrix());
346 | if (bringToFrontCurrentSticker) {
347 | stickers.remove(handlingSticker);
348 | stickers.add(handlingSticker);
349 | }
350 | if (onStickerOperationListener != null){
351 | onStickerOperationListener.onStickerTouchedDown(handlingSticker);
352 | }
353 | }
354 |
355 | if (currentIcon == null && handlingSticker == null) {
356 | return false;
357 | }
358 | invalidate();
359 | return true;
360 | }
361 |
362 | protected void onTouchUp(@NonNull MotionEvent event) {
363 | long currentTime = SystemClock.uptimeMillis();
364 |
365 | if (currentMode == ActionMode.ICON && currentIcon != null && handlingSticker != null) {
366 | currentIcon.onActionUp(this, event);
367 | }
368 |
369 | if (currentMode == ActionMode.DRAG
370 | && Math.abs(event.getX() - downX) < touchSlop
371 | && Math.abs(event.getY() - downY) < touchSlop
372 | && handlingSticker != null) {
373 | currentMode = ActionMode.CLICK;
374 | if (onStickerOperationListener != null) {
375 | onStickerOperationListener.onStickerClicked(handlingSticker);
376 | }
377 | if (currentTime - lastClickTime < minClickDelayTime) {
378 | if (onStickerOperationListener != null) {
379 | onStickerOperationListener.onStickerDoubleTapped(handlingSticker);
380 | }
381 | }
382 | }
383 |
384 | if (currentMode == ActionMode.DRAG && handlingSticker != null) {
385 | if (onStickerOperationListener != null) {
386 | onStickerOperationListener.onStickerDragFinished(handlingSticker);
387 | }
388 | }
389 |
390 | currentMode = ActionMode.NONE;
391 | lastClickTime = currentTime;
392 | }
393 |
394 | protected void handleCurrentMode(@NonNull MotionEvent event) {
395 | switch (currentMode) {
396 | case ActionMode.NONE:
397 | case ActionMode.CLICK:
398 | break;
399 | case ActionMode.DRAG:
400 | if (handlingSticker != null) {
401 | moveMatrix.set(downMatrix);
402 | moveMatrix.postTranslate(event.getX() - downX, event.getY() - downY);
403 | handlingSticker.setMatrix(moveMatrix);
404 | if (constrained) {
405 | constrainSticker(handlingSticker);
406 | }
407 | }
408 | break;
409 | case ActionMode.ZOOM_WITH_TWO_FINGER:
410 | if (handlingSticker != null) {
411 | float newDistance = calculateDistance(event);
412 | float newRotation = calculateRotation(event);
413 |
414 | moveMatrix.set(downMatrix);
415 | moveMatrix.postScale(newDistance / oldDistance, newDistance / oldDistance, midPoint.x,
416 | midPoint.y);
417 | moveMatrix.postRotate(newRotation - oldRotation, midPoint.x, midPoint.y);
418 | handlingSticker.setMatrix(moveMatrix);
419 | }
420 |
421 | break;
422 |
423 | case ActionMode.ICON:
424 | if (handlingSticker != null && currentIcon != null) {
425 | currentIcon.onActionMove(this, event);
426 | }
427 | break;
428 | }
429 | }
430 |
431 | public void zoomAndRotateCurrentSticker(@NonNull MotionEvent event) {
432 | zoomAndRotateSticker(handlingSticker, event);
433 | }
434 |
435 | public void zoomAndRotateSticker(@Nullable Sticker sticker, @NonNull MotionEvent event) {
436 | if (sticker != null) {
437 | float newDistance = calculateDistance(midPoint.x, midPoint.y, event.getX(), event.getY());
438 | float newRotation = calculateRotation(midPoint.x, midPoint.y, event.getX(), event.getY());
439 |
440 | moveMatrix.set(downMatrix);
441 | moveMatrix.postScale(newDistance / oldDistance, newDistance / oldDistance, midPoint.x,
442 | midPoint.y);
443 | moveMatrix.postRotate(newRotation - oldRotation, midPoint.x, midPoint.y);
444 | handlingSticker.setMatrix(moveMatrix);
445 | }
446 | }
447 |
448 | protected void constrainSticker(@NonNull Sticker sticker) {
449 | float moveX = 0;
450 | float moveY = 0;
451 | int width = getWidth();
452 | int height = getHeight();
453 | sticker.getMappedCenterPoint(currentCenterPoint, point, tmp);
454 | if (currentCenterPoint.x < 0) {
455 | moveX = -currentCenterPoint.x;
456 | }
457 |
458 | if (currentCenterPoint.x > width) {
459 | moveX = width - currentCenterPoint.x;
460 | }
461 |
462 | if (currentCenterPoint.y < 0) {
463 | moveY = -currentCenterPoint.y;
464 | }
465 |
466 | if (currentCenterPoint.y > height) {
467 | moveY = height - currentCenterPoint.y;
468 | }
469 |
470 | sticker.getMatrix().postTranslate(moveX, moveY);
471 | }
472 |
473 | @Nullable protected BitmapStickerIcon findCurrentIconTouched() {
474 | for (BitmapStickerIcon icon : icons) {
475 | float x = icon.getX() - downX;
476 | float y = icon.getY() - downY;
477 | float distance_pow_2 = x * x + y * y;
478 | if (distance_pow_2 <= Math.pow(icon.getIconRadius() + icon.getIconRadius(), 2)) {
479 | return icon;
480 | }
481 | }
482 |
483 | return null;
484 | }
485 |
486 | /**
487 | * find the touched Sticker
488 | **/
489 | @Nullable protected Sticker findHandlingSticker() {
490 | for (int i = stickers.size() - 1; i >= 0; i--) {
491 | if (isInStickerArea(stickers.get(i), downX, downY)) {
492 | return stickers.get(i);
493 | }
494 | }
495 | return null;
496 | }
497 |
498 | protected boolean isInStickerArea(@NonNull Sticker sticker, float downX, float downY) {
499 | tmp[0] = downX;
500 | tmp[1] = downY;
501 | return sticker.contains(tmp);
502 | }
503 |
504 | @NonNull protected PointF calculateMidPoint(@Nullable MotionEvent event) {
505 | if (event == null || event.getPointerCount() < 2) {
506 | midPoint.set(0, 0);
507 | return midPoint;
508 | }
509 | float x = (event.getX(0) + event.getX(1)) / 2;
510 | float y = (event.getY(0) + event.getY(1)) / 2;
511 | midPoint.set(x, y);
512 | return midPoint;
513 | }
514 |
515 | @NonNull protected PointF calculateMidPoint() {
516 | if (handlingSticker == null) {
517 | midPoint.set(0, 0);
518 | return midPoint;
519 | }
520 | handlingSticker.getMappedCenterPoint(midPoint, point, tmp);
521 | return midPoint;
522 | }
523 |
524 | /**
525 | * calculate rotation in line with two fingers and x-axis
526 | **/
527 | protected float calculateRotation(@Nullable MotionEvent event) {
528 | if (event == null || event.getPointerCount() < 2) {
529 | return 0f;
530 | }
531 | return calculateRotation(event.getX(0), event.getY(0), event.getX(1), event.getY(1));
532 | }
533 |
534 | protected float calculateRotation(float x1, float y1, float x2, float y2) {
535 | double x = x1 - x2;
536 | double y = y1 - y2;
537 | double radians = Math.atan2(y, x);
538 | return (float) Math.toDegrees(radians);
539 | }
540 |
541 | /**
542 | * calculate Distance in two fingers
543 | **/
544 | protected float calculateDistance(@Nullable MotionEvent event) {
545 | if (event == null || event.getPointerCount() < 2) {
546 | return 0f;
547 | }
548 | return calculateDistance(event.getX(0), event.getY(0), event.getX(1), event.getY(1));
549 | }
550 |
551 | protected float calculateDistance(float x1, float y1, float x2, float y2) {
552 | double x = x1 - x2;
553 | double y = y1 - y2;
554 |
555 | return (float) Math.sqrt(x * x + y * y);
556 | }
557 |
558 | @Override protected void onSizeChanged(int w, int h, int oldW, int oldH) {
559 | super.onSizeChanged(w, h, oldW, oldH);
560 | for (int i = 0; i < stickers.size(); i++) {
561 | Sticker sticker = stickers.get(i);
562 | if (sticker != null) {
563 | transformSticker(sticker);
564 | }
565 | }
566 | }
567 |
568 | /**
569 | * Sticker's drawable will be too bigger or smaller
570 | * This method is to transform it to fit
571 | * step 1:let the center of the sticker image is coincident with the center of the View.
572 | * step 2:Calculate the zoom and zoom
573 | **/
574 | protected void transformSticker(@Nullable Sticker sticker) {
575 | if (sticker == null) {
576 | Log.e(TAG, "transformSticker: the bitmapSticker is null or the bitmapSticker bitmap is null");
577 | return;
578 | }
579 |
580 | sizeMatrix.reset();
581 |
582 | float width = getWidth();
583 | float height = getHeight();
584 | float stickerWidth = sticker.getWidth();
585 | float stickerHeight = sticker.getHeight();
586 | //step 1
587 | float offsetX = (width - stickerWidth) / 2;
588 | float offsetY = (height - stickerHeight) / 2;
589 |
590 | sizeMatrix.postTranslate(offsetX, offsetY);
591 |
592 | //step 2
593 | float scaleFactor;
594 | if (width < height) {
595 | scaleFactor = width / stickerWidth;
596 | } else {
597 | scaleFactor = height / stickerHeight;
598 | }
599 |
600 | sizeMatrix.postScale(scaleFactor / 2f, scaleFactor / 2f, width / 2f, height / 2f);
601 |
602 | sticker.getMatrix().reset();
603 | sticker.setMatrix(sizeMatrix);
604 |
605 | invalidate();
606 | }
607 |
608 | public void flipCurrentSticker(int direction) {
609 | flip(handlingSticker, direction);
610 | }
611 |
612 | public void flip(@Nullable Sticker sticker, @Flip int direction) {
613 | if (sticker != null) {
614 | sticker.getCenterPoint(midPoint);
615 | if ((direction & FLIP_HORIZONTALLY) > 0) {
616 | sticker.getMatrix().preScale(-1, 1, midPoint.x, midPoint.y);
617 | sticker.setFlippedHorizontally(!sticker.isFlippedHorizontally());
618 | }
619 | if ((direction & FLIP_VERTICALLY) > 0) {
620 | sticker.getMatrix().preScale(1, -1, midPoint.x, midPoint.y);
621 | sticker.setFlippedVertically(!sticker.isFlippedVertically());
622 | }
623 |
624 | if (onStickerOperationListener != null) {
625 | onStickerOperationListener.onStickerFlipped(sticker);
626 | }
627 |
628 | invalidate();
629 | }
630 | }
631 |
632 | public boolean replace(@Nullable Sticker sticker) {
633 | return replace(sticker, true);
634 | }
635 |
636 | public boolean replace(@Nullable Sticker sticker, boolean needStayState) {
637 | if (handlingSticker != null && sticker != null) {
638 | float width = getWidth();
639 | float height = getHeight();
640 | if (needStayState) {
641 | sticker.setMatrix(handlingSticker.getMatrix());
642 | sticker.setFlippedVertically(handlingSticker.isFlippedVertically());
643 | sticker.setFlippedHorizontally(handlingSticker.isFlippedHorizontally());
644 | } else {
645 | handlingSticker.getMatrix().reset();
646 | // reset scale, angle, and put it in center
647 | float offsetX = (width - handlingSticker.getWidth()) / 2f;
648 | float offsetY = (height - handlingSticker.getHeight()) / 2f;
649 | sticker.getMatrix().postTranslate(offsetX, offsetY);
650 |
651 | float scaleFactor;
652 | if (width < height) {
653 | scaleFactor = width / handlingSticker.getDrawable().getIntrinsicWidth();
654 | } else {
655 | scaleFactor = height / handlingSticker.getDrawable().getIntrinsicHeight();
656 | }
657 | sticker.getMatrix().postScale(scaleFactor / 2f, scaleFactor / 2f, width / 2f, height / 2f);
658 | }
659 | int index = stickers.indexOf(handlingSticker);
660 | stickers.set(index, sticker);
661 | handlingSticker = sticker;
662 |
663 | invalidate();
664 | return true;
665 | } else {
666 | return false;
667 | }
668 | }
669 |
670 | public boolean remove(@Nullable Sticker sticker) {
671 | if (stickers.contains(sticker)) {
672 | stickers.remove(sticker);
673 | if (onStickerOperationListener != null) {
674 | onStickerOperationListener.onStickerDeleted(sticker);
675 | }
676 | if (handlingSticker == sticker) {
677 | handlingSticker = null;
678 | }
679 | invalidate();
680 |
681 | return true;
682 | } else {
683 | Log.d(TAG, "remove: the sticker is not in this StickerView");
684 |
685 | return false;
686 | }
687 | }
688 |
689 | public boolean removeCurrentSticker() {
690 | return remove(handlingSticker);
691 | }
692 |
693 | public void removeAllStickers() {
694 | stickers.clear();
695 | if (handlingSticker != null) {
696 | handlingSticker.release();
697 | handlingSticker = null;
698 | }
699 | invalidate();
700 | }
701 |
702 | @NonNull public StickerView addSticker(@NonNull Sticker sticker) {
703 | return addSticker(sticker, Sticker.Position.CENTER);
704 | }
705 |
706 | public StickerView addSticker(@NonNull final Sticker sticker,
707 | final @Sticker.Position int position) {
708 | if (ViewCompat.isLaidOut(this)) {
709 | addStickerImmediately(sticker, position);
710 | } else {
711 | post(new Runnable() {
712 | @Override public void run() {
713 | addStickerImmediately(sticker, position);
714 | }
715 | });
716 | }
717 | return this;
718 | }
719 |
720 | protected void addStickerImmediately(@NonNull Sticker sticker, @Sticker.Position int position) {
721 | setStickerPosition(sticker, position);
722 |
723 |
724 | float scaleFactor, widthScaleFactor, heightScaleFactor;
725 |
726 | widthScaleFactor = (float) getWidth() / sticker.getDrawable().getIntrinsicWidth();
727 | heightScaleFactor = (float) getHeight() / sticker.getDrawable().getIntrinsicHeight();
728 | scaleFactor = widthScaleFactor > heightScaleFactor ? heightScaleFactor : widthScaleFactor;
729 |
730 | sticker.getMatrix()
731 | .postScale(scaleFactor / 2, scaleFactor / 2, getWidth() / 2, getHeight() / 2);
732 |
733 | handlingSticker = sticker;
734 | stickers.add(sticker);
735 | if (onStickerOperationListener != null) {
736 | onStickerOperationListener.onStickerAdded(sticker);
737 | }
738 | invalidate();
739 | }
740 |
741 | protected void setStickerPosition(@NonNull Sticker sticker, @Sticker.Position int position) {
742 | float width = getWidth();
743 | float height = getHeight();
744 | float offsetX = width - sticker.getWidth();
745 | float offsetY = height - sticker.getHeight();
746 | if ((position & Sticker.Position.TOP) > 0) {
747 | offsetY /= 4f;
748 | } else if ((position & Sticker.Position.BOTTOM) > 0) {
749 | offsetY *= 3f / 4f;
750 | } else {
751 | offsetY /= 2f;
752 | }
753 | if ((position & Sticker.Position.LEFT) > 0) {
754 | offsetX /= 4f;
755 | } else if ((position & Sticker.Position.RIGHT) > 0) {
756 | offsetX *= 3f / 4f;
757 | } else {
758 | offsetX /= 2f;
759 | }
760 | sticker.getMatrix().postTranslate(offsetX, offsetY);
761 | }
762 |
763 | @NonNull public float[] getStickerPoints(@Nullable Sticker sticker) {
764 | float[] points = new float[8];
765 | getStickerPoints(sticker, points);
766 | return points;
767 | }
768 |
769 | public void getStickerPoints(@Nullable Sticker sticker, @NonNull float[] dst) {
770 | if (sticker == null) {
771 | Arrays.fill(dst, 0);
772 | return;
773 | }
774 | sticker.getBoundPoints(bounds);
775 | sticker.getMappedPoints(dst, bounds);
776 | }
777 |
778 | public void save(@NonNull File file) {
779 | try {
780 | StickerUtils.saveImageToGallery(file, createBitmap());
781 | StickerUtils.notifySystemGallery(getContext(), file);
782 | } catch (IllegalArgumentException | IllegalStateException ignored) {
783 | //
784 | }
785 | }
786 |
787 | @NonNull public Bitmap createBitmap() throws OutOfMemoryError {
788 | handlingSticker = null;
789 | Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
790 | Canvas canvas = new Canvas(bitmap);
791 | this.draw(canvas);
792 | return bitmap;
793 | }
794 |
795 | public int getStickerCount() {
796 | return stickers.size();
797 | }
798 |
799 | public boolean isNoneSticker() {
800 | return getStickerCount() == 0;
801 | }
802 |
803 | public boolean isLocked() {
804 | return locked;
805 | }
806 |
807 | @NonNull public StickerView setLocked(boolean locked) {
808 | this.locked = locked;
809 | invalidate();
810 | return this;
811 | }
812 |
813 | @NonNull public StickerView setMinClickDelayTime(int minClickDelayTime) {
814 | this.minClickDelayTime = minClickDelayTime;
815 | return this;
816 | }
817 |
818 | public int getMinClickDelayTime() {
819 | return minClickDelayTime;
820 | }
821 |
822 | public boolean isConstrained() {
823 | return constrained;
824 | }
825 |
826 | @NonNull public StickerView setConstrained(boolean constrained) {
827 | this.constrained = constrained;
828 | postInvalidate();
829 | return this;
830 | }
831 |
832 | @NonNull public StickerView setOnStickerOperationListener(
833 | @Nullable OnStickerOperationListener onStickerOperationListener) {
834 | this.onStickerOperationListener = onStickerOperationListener;
835 | return this;
836 | }
837 |
838 | @Nullable public OnStickerOperationListener getOnStickerOperationListener() {
839 | return onStickerOperationListener;
840 | }
841 |
842 | @Nullable public Sticker getCurrentSticker() {
843 | return handlingSticker;
844 | }
845 |
846 | @NonNull public List getIcons() {
847 | return icons;
848 | }
849 |
850 | public void setIcons(@NonNull List icons) {
851 | this.icons.clear();
852 | this.icons.addAll(icons);
853 | invalidate();
854 | }
855 |
856 | public interface OnStickerOperationListener {
857 | void onStickerAdded(@NonNull Sticker sticker);
858 |
859 | void onStickerClicked(@NonNull Sticker sticker);
860 |
861 | void onStickerDeleted(@NonNull Sticker sticker);
862 |
863 | void onStickerDragFinished(@NonNull Sticker sticker);
864 |
865 | void onStickerTouchedDown(@NonNull Sticker sticker);
866 |
867 | void onStickerZoomFinished(@NonNull Sticker sticker);
868 |
869 | void onStickerFlipped(@NonNull Sticker sticker);
870 |
871 | void onStickerDoubleTapped(@NonNull Sticker sticker);
872 | }
873 | }
874 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/TextSticker.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Matrix;
6 | import android.graphics.Rect;
7 | import android.graphics.Typeface;
8 | import android.graphics.drawable.Drawable;
9 | import android.support.annotation.ColorInt;
10 | import android.support.annotation.Dimension;
11 | import android.support.annotation.IntRange;
12 | import android.support.annotation.NonNull;
13 | import android.support.annotation.Nullable;
14 | import android.support.v4.content.ContextCompat;
15 | import android.text.Layout;
16 | import android.text.StaticLayout;
17 | import android.text.TextPaint;
18 |
19 | /**
20 | * Customize your sticker with text and image background.
21 | * You can place some text into a given region, however,
22 | * you can also add a plain text sticker. To support text
23 | * auto resizing , I take most of the code from AutoResizeTextView.
24 | * See https://adilatwork.blogspot.com/2014/08/android-textview-which-resizes-its-text.html
25 | * Notice: It's not efficient to add long text due to too much of
26 | * StaticLayout object allocation.
27 | * Created by liutao on 30/11/2016.
28 | */
29 |
30 | public class TextSticker extends Sticker {
31 |
32 | /**
33 | * Our ellipsis string.
34 | */
35 | private static final String mEllipsis = "\u2026";
36 |
37 | private final Context context;
38 | private final Rect realBounds;
39 | private final Rect textRect;
40 | private final TextPaint textPaint;
41 | private Drawable drawable;
42 | private StaticLayout staticLayout;
43 | private Layout.Alignment alignment;
44 | private String text;
45 |
46 | /**
47 | * Upper bounds for text size.
48 | * This acts as a starting point for resizing.
49 | */
50 | private float maxTextSizePixels;
51 |
52 | /**
53 | * Lower bounds for text size.
54 | */
55 | private float minTextSizePixels;
56 |
57 | /**
58 | * Line spacing multiplier.
59 | */
60 | private float lineSpacingMultiplier = 1.0f;
61 |
62 | /**
63 | * Additional line spacing.
64 | */
65 | private float lineSpacingExtra = 0.0f;
66 |
67 | public TextSticker(@NonNull Context context) {
68 | this(context, null);
69 | }
70 |
71 | public TextSticker(@NonNull Context context, @Nullable Drawable drawable) {
72 | this.context = context;
73 | this.drawable = drawable;
74 | if (drawable == null) {
75 | this.drawable = ContextCompat.getDrawable(context, R.drawable.sticker_transparent_background);
76 | }
77 | textPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
78 | realBounds = new Rect(0, 0, getWidth(), getHeight());
79 | textRect = new Rect(0, 0, getWidth(), getHeight());
80 | minTextSizePixels = convertSpToPx(6);
81 | maxTextSizePixels = convertSpToPx(32);
82 | alignment = Layout.Alignment.ALIGN_CENTER;
83 | textPaint.setTextSize(maxTextSizePixels);
84 | }
85 |
86 | @Override public void draw(@NonNull Canvas canvas) {
87 | Matrix matrix = getMatrix();
88 | canvas.save();
89 | canvas.concat(matrix);
90 | if (drawable != null) {
91 | drawable.setBounds(realBounds);
92 | drawable.draw(canvas);
93 | }
94 | canvas.restore();
95 |
96 | canvas.save();
97 | canvas.concat(matrix);
98 | if (textRect.width() == getWidth()) {
99 | int dy = getHeight() / 2 - staticLayout.getHeight() / 2;
100 | // center vertical
101 | canvas.translate(0, dy);
102 | } else {
103 | int dx = textRect.left;
104 | int dy = textRect.top + textRect.height() / 2 - staticLayout.getHeight() / 2;
105 | canvas.translate(dx, dy);
106 | }
107 | staticLayout.draw(canvas);
108 | canvas.restore();
109 | }
110 |
111 | @Override public int getWidth() {
112 | return drawable.getIntrinsicWidth();
113 | }
114 |
115 | @Override public int getHeight() {
116 | return drawable.getIntrinsicHeight();
117 | }
118 |
119 | @Override public void release() {
120 | super.release();
121 | if (drawable != null) {
122 | drawable = null;
123 | }
124 | }
125 |
126 | @NonNull @Override public TextSticker setAlpha(@IntRange(from = 0, to = 255) int alpha) {
127 | textPaint.setAlpha(alpha);
128 | return this;
129 | }
130 |
131 | @NonNull @Override public Drawable getDrawable() {
132 | return drawable;
133 | }
134 |
135 | @Override public TextSticker setDrawable(@NonNull Drawable drawable) {
136 | this.drawable = drawable;
137 | realBounds.set(0, 0, getWidth(), getHeight());
138 | textRect.set(0, 0, getWidth(), getHeight());
139 | return this;
140 | }
141 |
142 | @NonNull public TextSticker setDrawable(@NonNull Drawable drawable, @Nullable Rect region) {
143 | this.drawable = drawable;
144 | realBounds.set(0, 0, getWidth(), getHeight());
145 | if (region == null) {
146 | textRect.set(0, 0, getWidth(), getHeight());
147 | } else {
148 | textRect.set(region.left, region.top, region.right, region.bottom);
149 | }
150 | return this;
151 | }
152 |
153 | @NonNull public TextSticker setTypeface(@Nullable Typeface typeface) {
154 | textPaint.setTypeface(typeface);
155 | return this;
156 | }
157 |
158 | @NonNull public TextSticker setTextColor(@ColorInt int color) {
159 | textPaint.setColor(color);
160 | return this;
161 | }
162 |
163 | @NonNull public TextSticker setTextAlign(@NonNull Layout.Alignment alignment) {
164 | this.alignment = alignment;
165 | return this;
166 | }
167 |
168 | @NonNull public TextSticker setMaxTextSize(@Dimension(unit = Dimension.SP) float size) {
169 | textPaint.setTextSize(convertSpToPx(size));
170 | maxTextSizePixels = textPaint.getTextSize();
171 | return this;
172 | }
173 |
174 | /**
175 | * Sets the lower text size limit
176 | *
177 | * @param minTextSizeScaledPixels the minimum size to use for text in this view,
178 | * in scaled pixels.
179 | */
180 | @NonNull public TextSticker setMinTextSize(float minTextSizeScaledPixels) {
181 | minTextSizePixels = convertSpToPx(minTextSizeScaledPixels);
182 | return this;
183 | }
184 |
185 | @NonNull public TextSticker setLineSpacing(float add, float multiplier) {
186 | lineSpacingMultiplier = multiplier;
187 | lineSpacingExtra = add;
188 | return this;
189 | }
190 |
191 | @NonNull public TextSticker setText(@Nullable String text) {
192 | this.text = text;
193 | return this;
194 | }
195 |
196 | @Nullable public String getText() {
197 | return text;
198 | }
199 |
200 | /**
201 | * Resize this view's text size with respect to its width and height
202 | * (minus padding). You should always call this method after the initialization.
203 | */
204 | @NonNull public TextSticker resizeText() {
205 | final int availableHeightPixels = textRect.height();
206 |
207 | final int availableWidthPixels = textRect.width();
208 |
209 | final CharSequence text = getText();
210 |
211 | // Safety check
212 | // (Do not resize if the view does not have dimensions or if there is no text)
213 | if (text == null
214 | || text.length() <= 0
215 | || availableHeightPixels <= 0
216 | || availableWidthPixels <= 0
217 | || maxTextSizePixels <= 0) {
218 | return this;
219 | }
220 |
221 | float targetTextSizePixels = maxTextSizePixels;
222 | int targetTextHeightPixels =
223 | getTextHeightPixels(text, availableWidthPixels, targetTextSizePixels);
224 |
225 | // Until we either fit within our TextView
226 | // or we have reached our minimum text size,
227 | // incrementally try smaller sizes
228 | while (targetTextHeightPixels > availableHeightPixels
229 | && targetTextSizePixels > minTextSizePixels) {
230 | targetTextSizePixels = Math.max(targetTextSizePixels - 2, minTextSizePixels);
231 |
232 | targetTextHeightPixels =
233 | getTextHeightPixels(text, availableWidthPixels, targetTextSizePixels);
234 | }
235 |
236 | // If we have reached our minimum text size and the text still doesn't fit,
237 | // append an ellipsis
238 | // (NOTE: Auto-ellipsize doesn't work hence why we have to do it here)
239 | if (targetTextSizePixels == minTextSizePixels
240 | && targetTextHeightPixels > availableHeightPixels) {
241 | // Make a copy of the original TextPaint object for measuring
242 | TextPaint textPaintCopy = new TextPaint(textPaint);
243 | textPaintCopy.setTextSize(targetTextSizePixels);
244 |
245 | // Measure using a StaticLayout instance
246 | StaticLayout staticLayout =
247 | new StaticLayout(text, textPaintCopy, availableWidthPixels, Layout.Alignment.ALIGN_NORMAL,
248 | lineSpacingMultiplier, lineSpacingExtra, false);
249 |
250 | // Check that we have a least one line of rendered text
251 | if (staticLayout.getLineCount() > 0) {
252 | // Since the line at the specific vertical position would be cut off,
253 | // we must trim up to the previous line and add an ellipsis
254 | int lastLine = staticLayout.getLineForVertical(availableHeightPixels) - 1;
255 |
256 | if (lastLine >= 0) {
257 | int startOffset = staticLayout.getLineStart(lastLine);
258 | int endOffset = staticLayout.getLineEnd(lastLine);
259 | float lineWidthPixels = staticLayout.getLineWidth(lastLine);
260 | float ellipseWidth = textPaintCopy.measureText(mEllipsis);
261 |
262 | // Trim characters off until we have enough room to draw the ellipsis
263 | while (availableWidthPixels < lineWidthPixels + ellipseWidth) {
264 | endOffset--;
265 | lineWidthPixels =
266 | textPaintCopy.measureText(text.subSequence(startOffset, endOffset + 1).toString());
267 | }
268 |
269 | setText(text.subSequence(0, endOffset) + mEllipsis);
270 | }
271 | }
272 | }
273 | textPaint.setTextSize(targetTextSizePixels);
274 | staticLayout =
275 | new StaticLayout(this.text, textPaint, textRect.width(), alignment, lineSpacingMultiplier,
276 | lineSpacingExtra, true);
277 | return this;
278 | }
279 |
280 | /**
281 | * @return lower text size limit, in pixels.
282 | */
283 | public float getMinTextSizePixels() {
284 | return minTextSizePixels;
285 | }
286 |
287 | /**
288 | * Sets the text size of a clone of the view's {@link TextPaint} object
289 | * and uses a {@link StaticLayout} instance to measure the height of the text.
290 | *
291 | * @return the height of the text when placed in a view
292 | * with the specified width
293 | * and when the text has the specified size.
294 | */
295 | protected int getTextHeightPixels(@NonNull CharSequence source, int availableWidthPixels,
296 | float textSizePixels) {
297 | textPaint.setTextSize(textSizePixels);
298 | // It's not efficient to create a StaticLayout instance
299 | // every time when measuring, we can use StaticLayout.Builder
300 | // since api 23.
301 | StaticLayout staticLayout =
302 | new StaticLayout(source, textPaint, availableWidthPixels, Layout.Alignment.ALIGN_NORMAL,
303 | lineSpacingMultiplier, lineSpacingExtra, true);
304 | return staticLayout.getHeight();
305 | }
306 |
307 | /**
308 | * @return the number of pixels which scaledPixels corresponds to on the device.
309 | */
310 | private float convertSpToPx(float scaledPixels) {
311 | return scaledPixels * context.getResources().getDisplayMetrics().scaledDensity;
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/sticker/src/main/java/com/xiaopo/flying/sticker/ZoomIconEvent.java:
--------------------------------------------------------------------------------
1 | package com.xiaopo.flying.sticker;
2 |
3 | import android.view.MotionEvent;
4 |
5 | /**
6 | * @author wupanjie
7 | */
8 |
9 | public class ZoomIconEvent implements StickerIconEvent {
10 | @Override public void onActionDown(StickerView stickerView, MotionEvent event) {
11 |
12 | }
13 |
14 | @Override public void onActionMove(StickerView stickerView, MotionEvent event) {
15 | stickerView.zoomAndRotateCurrentSticker(event);
16 | }
17 |
18 | @Override public void onActionUp(StickerView stickerView, MotionEvent event) {
19 | if (stickerView.getOnStickerOperationListener() != null) {
20 | stickerView.getOnStickerOperationListener()
21 | .onStickerZoomFinished(stickerView.getCurrentSticker());
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-hdpi/sticker_ic_close_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-hdpi/sticker_ic_close_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-hdpi/sticker_ic_flip_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-hdpi/sticker_ic_flip_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-hdpi/sticker_ic_scale_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-hdpi/sticker_ic_scale_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-mdpi/sticker_ic_close_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-mdpi/sticker_ic_close_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-mdpi/sticker_ic_flip_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-mdpi/sticker_ic_flip_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-mdpi/sticker_ic_scale_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-mdpi/sticker_ic_scale_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-xhdpi/sticker_ic_close_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-xhdpi/sticker_ic_close_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-xhdpi/sticker_ic_flip_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-xhdpi/sticker_ic_flip_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-xhdpi/sticker_ic_scale_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-xhdpi/sticker_ic_scale_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-xxhdpi/sticker_ic_close_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-xxhdpi/sticker_ic_close_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-xxhdpi/sticker_ic_flip_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-xxhdpi/sticker_ic_flip_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-xxhdpi/sticker_ic_scale_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-xxhdpi/sticker_ic_scale_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-xxxhdpi/sticker_ic_close_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-xxxhdpi/sticker_ic_close_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-xxxhdpi/sticker_ic_flip_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-xxxhdpi/sticker_ic_flip_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable-xxxhdpi/sticker_ic_scale_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuapnjie/StickerView/4a82f82fba04aacdaffea78c5b63912114b83c7a/sticker/src/main/res/drawable-xxxhdpi/sticker_ic_scale_white_18dp.png
--------------------------------------------------------------------------------
/sticker/src/main/res/drawable/sticker_transparent_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sticker/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/sticker/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Sticker
3 |
4 |
--------------------------------------------------------------------------------