├── .gitignore
├── demo.gif
├── src
├── main
│ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── ids.xml
│ │ │ ├── styles.xml
│ │ │ └── attrs.xml
│ │ └── drawable
│ │ │ ├── flip.png
│ │ │ ├── remove.png
│ │ │ └── zoominout.png
│ ├── java
│ │ └── com
│ │ │ └── knef
│ │ │ └── stickerview
│ │ │ ├── IStickerOperation.kt
│ │ │ ├── StickerGifView.kt
│ │ │ ├── StickerImageView.java
│ │ │ ├── AllStickers.kt
│ │ │ ├── StickerTextView.java
│ │ │ ├── util
│ │ │ ├── AutoResizeTextView.java
│ │ │ └── GifView.java
│ │ │ └── StickerView.java
│ └── AndroidManifest.xml
└── androidTest
│ └── java
│ └── com
│ └── knef
│ └── stickerview
│ └── ApplicationTest.java
├── proguard-rules.pro
├── README.md
├── android-StickerView.iml
└── LICENSE.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /git
3 | *.iml
4 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tonguetech/Android-StickerView/HEAD/demo.gif
--------------------------------------------------------------------------------
/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | StickerView
3 |
4 |
--------------------------------------------------------------------------------
/src/main/res/drawable/flip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tonguetech/Android-StickerView/HEAD/src/main/res/drawable/flip.png
--------------------------------------------------------------------------------
/src/main/res/drawable/remove.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tonguetech/Android-StickerView/HEAD/src/main/res/drawable/remove.png
--------------------------------------------------------------------------------
/src/main/res/drawable/zoominout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tonguetech/Android-StickerView/HEAD/src/main/res/drawable/zoominout.png
--------------------------------------------------------------------------------
/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | //For GifView Declaration
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/java/com/knef/stickerview/IStickerOperation.kt:
--------------------------------------------------------------------------------
1 | package com.knef.stickerview
2 |
3 | interface IStickerOperation {
4 | fun onSelect(tag: String)
5 | fun onDelete(tag: String)
6 | }
--------------------------------------------------------------------------------
/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/androidTest/java/com/knef/stickerview/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.knef.stickerview;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/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 /Users/cheungchingai/Library/Android/sdk/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 |
--------------------------------------------------------------------------------
/src/main/java/com/knef/stickerview/StickerGifView.kt:
--------------------------------------------------------------------------------
1 | package com.knef.stickerview
2 |
3 | import android.content.Context
4 | import android.graphics.PointF
5 | import android.util.AttributeSet
6 | import android.util.Log
7 | import android.view.View
8 | import com.knef.stickerview.util.GifView
9 |
10 | /**
11 | * Created by dastaniqbal on 03/07/2017.
12 | * ask2iqbal@gmail.com
13 | * 03/07/2017 3:33
14 | */
15 |
16 | class StickerGifView : StickerView{
17 | private var gif_main: GifView? = null
18 | var xy = PointF()
19 | var size = PointF()
20 | var stickerPath: String? = null
21 |
22 | constructor(context: Context) : super(context)
23 |
24 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
25 |
26 | constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
27 |
28 | override fun getMainView(): View? {
29 | if (gif_main == null) {
30 | gif_main = GifView(context)
31 | }
32 | return gif_main
33 | }
34 |
35 | override fun updateSize() {
36 | size = PointF(this.width.toFloat(), this.height.toFloat())
37 | Log.d("StickerGifView", "Size ${size.x}, ${size.y}")
38 | }
39 |
40 | override fun updateXY() {
41 | xy = PointF((x + measuredWidth / 2), (y + measuredHeight / 2))
42 | val pos = IntArray(2)
43 | getLocationOnScreen(pos)
44 | Log.d("StickerGifView", "LocationScreen ${pos[0]}, ${pos[1]}")
45 | Log.d("StickerGifView", "centerX= ${xy.x}, centerY= ${xy.y}")
46 | Log.d("StickerGifView", "measureW= ${measuredWidth}, measureH= ${measuredHeight}")
47 | }
48 |
49 | fun setGifPath(path: String) {
50 | this.stickerPath = path
51 | this.gif_main?.setGifPath(path)
52 | this.gif_main?.play()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/knef/stickerview/StickerImageView.java:
--------------------------------------------------------------------------------
1 | package com.knef.stickerview;
2 |
3 |
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.drawable.BitmapDrawable;
7 | import android.graphics.drawable.Drawable;
8 | import android.util.AttributeSet;
9 | import android.view.View;
10 | import android.widget.ImageView;
11 |
12 |
13 | public class StickerImageView extends StickerView {
14 |
15 | private String owner_id;
16 | private ImageView iv_main;
17 |
18 | @Override
19 | protected void updateXY() {}
20 |
21 | @Override
22 | protected void updateSize() {}
23 |
24 | public StickerImageView(Context context) {
25 | super(context);
26 | }
27 |
28 | public StickerImageView(Context context, AttributeSet attrs) {
29 | super(context, attrs);
30 | }
31 |
32 | public StickerImageView(Context context, AttributeSet attrs, int defStyle) {
33 | super(context, attrs, defStyle);
34 | }
35 |
36 | public void setOwnerId(String owner_id){
37 | this.owner_id = owner_id;
38 | }
39 |
40 | public String getOwnerId(){
41 | return this.owner_id;
42 | }
43 |
44 | @Override
45 | public View getMainView() {
46 | if(this.iv_main == null) {
47 | this.iv_main = new ImageView(getContext());
48 | this.iv_main.setScaleType(ImageView.ScaleType.FIT_XY);
49 | }
50 | return iv_main;
51 | }
52 | public void setImageBitmap(Bitmap bmp){
53 | this.iv_main.setImageBitmap(bmp);
54 | }
55 |
56 | public void setImageResource(int res_id){
57 | this.iv_main.setImageResource(res_id);
58 | }
59 |
60 | public void setImageDrawable(Drawable drawable){ this.iv_main.setImageDrawable(drawable); }
61 |
62 | public Bitmap getImageBitmap(){ return ((BitmapDrawable)this.iv_main.getDrawable()).getBitmap() ; }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # android-StickerView
2 |
3 | ## Description
4 | StickerView is a android UI library that make you able to use
5 | Single hand gesture to rotate, scale and flip the ‘sticker’.
6 |
7 |
8 | 
9 |
10 |
11 | ## Usage
12 |
13 | The usage is simple, just like adding a child view.
14 | For example, if you have an activity:
15 |
16 | ```java
17 | FrameLayout canvas = findViewById(R.id.vg_canvas);
18 |
19 | //……
20 |
21 | // add a stickerImage to canvas
22 | StickerImageView iv_sticker = new StickerImageView(getContext());
23 | iv_sticker.setImageDrawable(((ImageView)view.findViewById(R.id.iv_sticker)).getDrawable());
24 | canvas.adView(iv_sticker);
25 |
26 | // add a stickerText to canvas
27 | StickerTextView tv_sticker = new StickerTextView(getContext());
28 | tv_sticker.setText(“call me baby”);
29 | canvas.addView(tv_sticker);
30 |
31 | // add a stickerText to canvas
32 | StickerGifView gifSticker = new StickerGifView(this);
33 | gifSticker.setGifPath(stkrName);
34 | String tag = String.valueOf( + 1);
35 | gifSticker.setStickerTag(tag);
36 | gifSticker.setOnSelectListener(stickerOperation);
37 | canvas.addView(gifSticker);
38 |
39 | //Sticker Listener
40 | private IStickerOperation stickerOperation=new IStickerOperation() {
41 | @Override
42 | public void onSelect(@NotNull String tag) {
43 |
44 | }
45 |
46 | @Override
47 | public void onDelete(@NotNull String tag) {
48 |
49 | }
50 | };
51 | ```
52 | Orignal GifView Repository : https://github.com/Cutta/GifView
53 |
54 | # License
55 | Copyright 2017 Iqbal Ahmed.
56 |
57 | Copyright 2017 ken_cheung.
58 |
59 | Licensed under the Apache License, Version 2.0 (the "License");
60 | you may not use this file except in compliance with the License.
61 | You may obtain a copy of the License at
62 |
63 | http://www.apache.org/licenses/LICENSE-2.0
64 |
65 | Unless required by applicable law or agreed to in writing, software
66 | distributed under the License is distributed on an "AS IS" BASIS,
67 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
68 | See the License for the specific language governing permissions and
69 | limitations under the License.
70 |
--------------------------------------------------------------------------------
/src/main/java/com/knef/stickerview/AllStickers.kt:
--------------------------------------------------------------------------------
1 | package com.knef.stickerview
2 |
3 | import android.view.View
4 | import java.util.*
5 |
6 | /**
7 | * Created by dastaniqbal on 04/07/2017.
8 | * ask2iqbal@gmail.com
9 | * 04/07/2017 4:49
10 | */
11 |
12 | class AllStickers {
13 | val stickerViewStickers = HashMap()
14 | var currentSticker: StickerView? = null
15 | private set
16 |
17 | fun addInStickerViewList(obj: String, view: View) {
18 | stickerViewStickers.put(obj, view)
19 | }
20 |
21 | fun clearStickerViewList() {
22 | stickerViewStickers.clear()
23 | }
24 |
25 | fun selectUnselectGifSticker(tag: String) {
26 | for ((_, value) in stickerViewStickers) {
27 | val view = value as StickerView
28 | if (view.stickerTag == tag) {
29 | view.select(true)
30 | currentSticker = view
31 | } else {
32 | view.select(false)
33 | }
34 | }
35 | }
36 |
37 | fun lockUnlockStickers(islock: Boolean) {
38 | for ((_, value) in stickerViewStickers) {
39 | val view = value as StickerView
40 | view.select(!islock)
41 | }
42 | }
43 |
44 | fun getStickersHashMap(): TreeMap> {
45 | //Collections.sort(stickers)
46 | val hashMap = TreeMap>()
47 | for ((_, value) in stickerViewStickers) {
48 | val view = value as StickerView
49 | view.setVisible(false)
50 | val truncateLast2Digit = (view.durationStart - view.durationStart % 100) / 100
51 | val exitStickers = hashMap[truncateLast2Digit]
52 | if (exitStickers != null && exitStickers.size > 0) {
53 | exitStickers.add(view)
54 | hashMap.put(truncateLast2Digit, exitStickers)
55 | } else {
56 | val stickerList = ArrayList()
57 | stickerList.add(view)
58 | hashMap.put(truncateLast2Digit, stickerList)
59 | }
60 | }
61 | return hashMap
62 | }
63 |
64 | fun showOrHideStickers(isShow: Boolean) {
65 | for ((_, value) in stickerViewStickers) {
66 | val view = value as StickerView
67 | view.setVisible(isShow)
68 | }
69 | }
70 |
71 | fun removeSticker(tag: String) {
72 | stickerViewStickers.remove(tag)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/knef/stickerview/StickerTextView.java:
--------------------------------------------------------------------------------
1 | package com.knef.stickerview;
2 |
3 |
4 | import android.content.Context;
5 | import android.graphics.Color;
6 | import android.util.AttributeSet;
7 | import android.view.Gravity;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.FrameLayout;
11 |
12 | import com.knef.stickerview.util.AutoResizeTextView;
13 |
14 | /**
15 | * Created by cheungchingai on 6/15/15.
16 | */
17 | public class StickerTextView extends StickerView{
18 | private AutoResizeTextView tv_main;
19 |
20 | @Override
21 | protected void updateXY() {}
22 |
23 | @Override
24 | protected void updateSize() {}
25 |
26 | public StickerTextView(Context context) {
27 | super(context);
28 | }
29 |
30 | public StickerTextView(Context context, AttributeSet attrs) {
31 | super(context, attrs);
32 | }
33 |
34 | public StickerTextView(Context context, AttributeSet attrs, int defStyle) {
35 | super(context, attrs, defStyle);
36 | }
37 |
38 | @Override
39 | public View getMainView() {
40 | if(tv_main != null)
41 | return tv_main;
42 |
43 | tv_main = new AutoResizeTextView(getContext());
44 | //tv_main.setTextSize(22);
45 | tv_main.setTextColor(Color.WHITE);
46 | tv_main.setGravity(Gravity.CENTER);
47 | tv_main.setTextSize(400);
48 | tv_main.setShadowLayer(4, 0, 0, Color.BLACK);
49 | tv_main.setMaxLines(1);
50 | FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
51 | ViewGroup.LayoutParams.MATCH_PARENT,
52 | ViewGroup.LayoutParams.MATCH_PARENT
53 | );
54 | params.gravity = Gravity.CENTER;
55 | tv_main.setLayoutParams(params);
56 | if(getImageViewFlip()!=null)
57 | getImageViewFlip().setVisibility(View.GONE);
58 | return tv_main;
59 | }
60 |
61 | public void setText(String text){
62 | if(tv_main!=null)
63 | tv_main.setText(text);
64 | }
65 |
66 | public String getText(){
67 | if(tv_main!=null)
68 | return tv_main.getText().toString();
69 |
70 | return null;
71 | }
72 |
73 | public static float pixelsToSp(Context context, float px) {
74 | float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
75 | return px/scaledDensity;
76 | }
77 |
78 | @Override
79 | protected void onScaling(boolean scaleUp) {
80 | super.onScaling(scaleUp);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/android-StickerView.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | generateDebugSources
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/main/java/com/knef/stickerview/util/AutoResizeTextView.java:
--------------------------------------------------------------------------------
1 | package com.knef.stickerview.util;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.res.Resources;
6 | import android.graphics.RectF;
7 | import android.os.Build;
8 | import android.text.Layout.Alignment;
9 | import android.text.StaticLayout;
10 | import android.text.TextPaint;
11 | import android.util.AttributeSet;
12 | import android.util.SparseIntArray;
13 | import android.util.TypedValue;
14 | import android.widget.TextView;
15 |
16 | public class AutoResizeTextView extends TextView {
17 | private interface SizeTester {
18 | /**
19 | *
20 | * @param suggestedSize
21 | * Size of text to be tested
22 | * @param availableSpace
23 | * available space in which text must fit
24 | * @return an integer < 0 if after applying {@code suggestedSize} to
25 | * text, it takes less space than {@code availableSpace}, > 0
26 | * otherwise
27 | */
28 | public int onTestSize(int suggestedSize, RectF availableSpace);
29 | }
30 |
31 | private RectF mTextRect = new RectF();
32 |
33 | private RectF mAvailableSpaceRect;
34 |
35 | private SparseIntArray mTextCachedSizes;
36 |
37 | private TextPaint mPaint;
38 |
39 | private float mMaxTextSize;
40 |
41 | private float mSpacingMult = 1.0f;
42 |
43 | private float mSpacingAdd = 0.0f;
44 |
45 | private float mMinTextSize = 20;
46 |
47 | private int mWidthLimit;
48 |
49 | private static final int NO_LINE_LIMIT = -1;
50 | private int mMaxLines;
51 |
52 | private boolean mEnableSizeCache = true;
53 | private boolean mInitiallized;
54 |
55 | public AutoResizeTextView(Context context) {
56 | super(context);
57 | initialize();
58 | }
59 |
60 | public AutoResizeTextView(Context context, AttributeSet attrs) {
61 | super(context, attrs);
62 | initialize();
63 | }
64 |
65 | public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
66 | super(context, attrs, defStyle);
67 | initialize();
68 | }
69 |
70 | private void initialize() {
71 | mPaint = new TextPaint(getPaint());
72 | mMaxTextSize = getTextSize();
73 | mAvailableSpaceRect = new RectF();
74 | mTextCachedSizes = new SparseIntArray();
75 | if (mMaxLines == 0) {
76 | // no value was assigned during construction
77 | mMaxLines = NO_LINE_LIMIT;
78 | }
79 | mInitiallized = true;
80 | }
81 |
82 | @Override
83 | public void setText(final CharSequence text, BufferType type) {
84 | super.setText(text, type);
85 | adjustTextSize(text.toString());
86 | }
87 |
88 | @Override
89 | public void setTextSize(float size) {
90 | mMaxTextSize = size;
91 | mTextCachedSizes.clear();
92 | adjustTextSize(getText().toString());
93 | }
94 |
95 | @Override
96 | public void setMaxLines(int maxlines) {
97 | super.setMaxLines(maxlines);
98 | mMaxLines = maxlines;
99 | reAdjust();
100 | }
101 |
102 | public int getMaxLines() {
103 | return mMaxLines;
104 | }
105 |
106 | @Override
107 | public void setSingleLine() {
108 | super.setSingleLine();
109 | mMaxLines = 1;
110 | reAdjust();
111 | }
112 |
113 | @Override
114 | public void setSingleLine(boolean singleLine) {
115 | super.setSingleLine(singleLine);
116 | if (singleLine) {
117 | mMaxLines = 1;
118 | } else {
119 | mMaxLines = NO_LINE_LIMIT;
120 | }
121 | reAdjust();
122 | }
123 |
124 | @Override
125 | public void setLines(int lines) {
126 | super.setLines(lines);
127 | mMaxLines = lines;
128 | reAdjust();
129 | }
130 |
131 | @Override
132 | public void setTextSize(int unit, float size) {
133 | Context c = getContext();
134 | Resources r;
135 |
136 | if (c == null)
137 | r = Resources.getSystem();
138 | else
139 | r = c.getResources();
140 | mMaxTextSize = TypedValue.applyDimension(unit, size,
141 | r.getDisplayMetrics());
142 | mTextCachedSizes.clear();
143 | adjustTextSize(getText().toString());
144 | }
145 |
146 | @Override
147 | public void setLineSpacing(float add, float mult) {
148 | super.setLineSpacing(add, mult);
149 | mSpacingMult = mult;
150 | mSpacingAdd = add;
151 | }
152 |
153 | /**
154 | * Set the lower text size limit and invalidate the view
155 | *
156 | * @param minTextSize
157 | */
158 | public void setMinTextSize(float minTextSize) {
159 | mMinTextSize = minTextSize;
160 | reAdjust();
161 | }
162 |
163 | private void reAdjust() {
164 | adjustTextSize(getText().toString());
165 | }
166 |
167 | private void adjustTextSize(String string) {
168 | if (!mInitiallized) {
169 | return;
170 | }
171 | int startSize = (int) mMinTextSize;
172 | int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom()
173 | - getCompoundPaddingTop();
174 | mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
175 | - getCompoundPaddingRight();
176 | mAvailableSpaceRect.right = mWidthLimit;
177 | mAvailableSpaceRect.bottom = heightLimit;
178 | super.setTextSize(
179 | TypedValue.COMPLEX_UNIT_PX,
180 | efficientTextSizeSearch(startSize, (int) mMaxTextSize,
181 | mSizeTester, mAvailableSpaceRect));
182 | }
183 |
184 | private final SizeTester mSizeTester = new SizeTester() {
185 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
186 | @Override
187 | public int onTestSize(int suggestedSize, RectF availableSPace) {
188 | mPaint.setTextSize(suggestedSize);
189 | String text = getText().toString();
190 | boolean singleline = getMaxLines() == 1;
191 | if (singleline) {
192 | mTextRect.bottom = mPaint.getFontSpacing();
193 | mTextRect.right = mPaint.measureText(text);
194 | } else {
195 | StaticLayout layout = new StaticLayout(text, mPaint,
196 | mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
197 | mSpacingAdd, true);
198 | // return early if we have more lines
199 | if (getMaxLines() != NO_LINE_LIMIT
200 | && layout.getLineCount() > getMaxLines()) {
201 | return 1;
202 | }
203 | mTextRect.bottom = layout.getHeight();
204 | int maxWidth = -1;
205 | for (int i = 0; i < layout.getLineCount(); i++) {
206 | if (maxWidth < layout.getLineWidth(i)) {
207 | maxWidth = (int) layout.getLineWidth(i);
208 | }
209 | }
210 | mTextRect.right = maxWidth;
211 | }
212 |
213 | mTextRect.offsetTo(0, 0);
214 | if (availableSPace.contains(mTextRect)) {
215 | // may be too small, don't worry we will find the best match
216 | return -1;
217 | } else {
218 | // too big
219 | return 1;
220 | }
221 | }
222 | };
223 |
224 | /**
225 | * Enables or disables size caching, enabling it will improve performance
226 | * where you are animating a value inside TextView. This stores the font
227 | * size against getText().length() Be careful though while enabling it as 0
228 | * takes more space than 1 on some fonts and so on.
229 | *
230 | * @param enable
231 | * enable font size caching
232 | */
233 | public void enableSizeCache(boolean enable) {
234 | mEnableSizeCache = enable;
235 | mTextCachedSizes.clear();
236 | adjustTextSize(getText().toString());
237 | }
238 |
239 | private int efficientTextSizeSearch(int start, int end,
240 | SizeTester sizeTester, RectF availableSpace) {
241 | if (!mEnableSizeCache) {
242 | return binarySearch(start, end, sizeTester, availableSpace);
243 | }
244 | String text = getText().toString();
245 | int key = text == null ? 0 : text.length();
246 | int size = mTextCachedSizes.get(key);
247 | if (size != 0) {
248 | return size;
249 | }
250 | size = binarySearch(start, end, sizeTester, availableSpace);
251 | mTextCachedSizes.put(key, size);
252 | return size;
253 | }
254 |
255 | private static int binarySearch(int start, int end, SizeTester sizeTester,
256 | RectF availableSpace) {
257 | int lastBest = start;
258 | int lo = start;
259 | int hi = end - 1;
260 | int mid = 0;
261 | while (lo <= hi) {
262 | mid = (lo + hi) >>> 1;
263 | int midValCmp = sizeTester.onTestSize(mid, availableSpace);
264 | if (midValCmp < 0) {
265 | lastBest = lo;
266 | lo = mid + 1;
267 | } else if (midValCmp > 0) {
268 | hi = mid - 1;
269 | lastBest = hi;
270 | } else {
271 | return mid;
272 | }
273 | }
274 | // make sure to return last best
275 | // this is what should always be returned
276 | return lastBest;
277 |
278 | }
279 |
280 | @Override
281 | protected void onTextChanged(final CharSequence text, final int start,
282 | final int before, final int after) {
283 | super.onTextChanged(text, start, before, after);
284 | reAdjust();
285 | }
286 |
287 | @Override
288 | protected void onSizeChanged(int width, int height, int oldwidth,
289 | int oldheight) {
290 | mTextCachedSizes.clear();
291 | super.onSizeChanged(width, height, oldwidth, oldheight);
292 | if (width != oldwidth || height != oldheight) {
293 | reAdjust();
294 | }
295 | }
296 | }
--------------------------------------------------------------------------------
/src/main/java/com/knef/stickerview/util/GifView.java:
--------------------------------------------------------------------------------
1 | package com.knef.stickerview.util;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Canvas;
7 | import android.graphics.Movie;
8 | import android.os.Build;
9 | import android.util.AttributeSet;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 |
13 | import com.knef.stickerview.R;
14 |
15 | import java.io.FileInputStream;
16 | import java.io.FileNotFoundException;
17 |
18 | /**
19 | * Created by Cuneyt on 4.10.2015.
20 | * original source code https://github.com/Cutta/GifView
21 | * Gifview
22 | */
23 | public class GifView extends View implements View.OnTouchListener {
24 |
25 | private static final int DEFAULT_MOVIE_VIEW_DURATION = 1000;
26 |
27 | private int mMovieResourceId;
28 | private Movie movie;
29 |
30 | private long mMovieStart;
31 | private int mCurrentAnimationTime;
32 |
33 |
34 | /**
35 | * Position for drawing animation frames in the center of the view.
36 | */
37 | private float mLeft;
38 | private float mTop;
39 |
40 | /**
41 | * Scaling factor to fit the animation within view bounds.
42 | */
43 | private float mScale;
44 |
45 | /**
46 | * Scaled movie frames width and height.
47 | */
48 | private int mMeasuredMovieWidth;
49 | private int mMeasuredMovieHeight;
50 |
51 | private volatile boolean mPaused;
52 | private boolean mVisible = true;
53 | private Context context;
54 |
55 | public GifView(Context context) {
56 | this(context, null, R.styleable.CustomTheme_gifViewStyle);
57 | init(context);
58 | }
59 |
60 | public GifView(Context context, AttributeSet attrs) {
61 | this(context, attrs, R.styleable.CustomTheme_gifViewStyle);
62 | init(context);
63 | }
64 |
65 | public GifView(Context context, AttributeSet attrs, int defStyle) {
66 | super(context, attrs, defStyle);
67 | setViewAttributes(context, attrs, defStyle);
68 | init(context);
69 | }
70 |
71 | private void init(Context context) {
72 | this.context = context;
73 | }
74 |
75 | public void enableTouchListener(boolean touchEnable) {
76 | if (touchEnable) {
77 | // setOnTouchListener(this);
78 | } else {
79 | setOnTouchListener(null);
80 | }
81 | }
82 |
83 | @SuppressLint("NewApi")
84 | private void setViewAttributes(Context context, AttributeSet attrs, int defStyle) {
85 |
86 | /**
87 | * Starting from HONEYCOMB(Api Level:11) have to turn off HW acceleration to draw
88 | * Movie on Canvas.
89 | */
90 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
91 | setLayerType(View.LAYER_TYPE_SOFTWARE, null);
92 | }
93 |
94 | final TypedArray array = context.obtainStyledAttributes(attrs,
95 | R.styleable.GifView, defStyle, R.style.Widget_GifView);
96 |
97 | //-1 is default value
98 | mMovieResourceId = array.getResourceId(R.styleable.GifView_gif, -1);
99 | mPaused = array.getBoolean(R.styleable.GifView_paused, false);
100 |
101 | array.recycle();
102 |
103 | if (mMovieResourceId != -1) {
104 | movie = Movie.decodeStream(getResources().openRawResource(mMovieResourceId));
105 | }
106 | }
107 |
108 | public void setGifResource(int movieResourceId) {
109 | this.mMovieResourceId = movieResourceId;
110 | movie = Movie.decodeStream(getResources().openRawResource(mMovieResourceId));
111 | requestLayout();
112 | }
113 |
114 | public void setGifPath(String path) throws FileNotFoundException {
115 | movie = Movie.decodeStream(new FileInputStream(path));
116 | requestLayout();
117 | }
118 |
119 | public int getGifResource() {
120 | return this.mMovieResourceId;
121 | }
122 |
123 |
124 | public void play() {
125 | if (this.mPaused) {
126 | this.mPaused = false;
127 |
128 | /**
129 | * Calculate new movie start time, so that it resumes from the same
130 | * frame.
131 | */
132 | mMovieStart = android.os.SystemClock.uptimeMillis() - mCurrentAnimationTime;
133 |
134 | invalidate();
135 | }
136 | }
137 |
138 | public void pause() {
139 | if (!this.mPaused) {
140 | this.mPaused = true;
141 |
142 | invalidate();
143 | }
144 |
145 | }
146 |
147 |
148 | public boolean isPaused() {
149 | return this.mPaused;
150 | }
151 |
152 | public boolean isPlaying() {
153 | return !this.mPaused;
154 | }
155 |
156 | @Override
157 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
158 |
159 | if (movie != null) {
160 | int movieWidth = movie.width();
161 | int movieHeight = movie.height();
162 |
163 | /*
164 | * Calculate horizontal scaling
165 | */
166 | float scaleH = 1f;
167 | int measureModeWidth = MeasureSpec.getMode(widthMeasureSpec);
168 |
169 | if (measureModeWidth != MeasureSpec.UNSPECIFIED) {
170 | int maximumWidth = MeasureSpec.getSize(widthMeasureSpec);
171 | if (movieWidth > maximumWidth) {
172 | scaleH = (float) movieWidth / (float) maximumWidth;
173 | }
174 | }
175 |
176 | /*
177 | * calculate vertical scaling
178 | */
179 | float scaleW = 1f;
180 | int measureModeHeight = MeasureSpec.getMode(heightMeasureSpec);
181 |
182 | if (measureModeHeight != MeasureSpec.UNSPECIFIED) {
183 | int maximumHeight = MeasureSpec.getSize(heightMeasureSpec);
184 | if (movieHeight > maximumHeight) {
185 | scaleW = (float) movieHeight / (float) maximumHeight;
186 | }
187 | }
188 |
189 | /*
190 | * calculate overall scale
191 | */
192 | mScale = 1f / Math.max(scaleH, scaleW);
193 |
194 | mMeasuredMovieWidth = (int) (movieWidth * mScale);
195 | mMeasuredMovieHeight = (int) (movieHeight * mScale);
196 |
197 | setMeasuredDimension(mMeasuredMovieWidth, mMeasuredMovieHeight);
198 |
199 | } else {
200 | /*
201 | * No movie set, just set minimum available size.
202 | */
203 | setMeasuredDimension(getSuggestedMinimumWidth(), getSuggestedMinimumHeight());
204 | }
205 | }
206 |
207 | @Override
208 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
209 | super.onLayout(changed, l, t, r, b);
210 | /*
211 | * Calculate mLeft / mTop for drawing in center
212 | */
213 | mLeft = (getWidth() - mMeasuredMovieWidth) / 2f;
214 | mTop = (getHeight() - mMeasuredMovieHeight) / 2f;
215 |
216 | mVisible = getVisibility() == View.VISIBLE;
217 | }
218 |
219 | @Override
220 | protected void onDraw(Canvas canvas) {
221 | if (movie != null) {
222 | if (!mPaused) {
223 | updateAnimationTime();
224 | drawMovieFrame(canvas);
225 | invalidateView();
226 | } else {
227 | drawMovieFrame(canvas);
228 | }
229 | }
230 | }
231 |
232 | /**
233 | * Invalidates view only if it is mVisible.
234 | *
235 | * {@link #postInvalidateOnAnimation()} is used for Jelly Bean and higher.
236 | */
237 | @SuppressLint("NewApi")
238 | private void invalidateView() {
239 | if (mVisible) {
240 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
241 | postInvalidateOnAnimation();
242 | } else {
243 | invalidate();
244 | }
245 | }
246 | }
247 |
248 | /**
249 | * Calculate current animation time
250 | */
251 | private void updateAnimationTime() {
252 | long now = android.os.SystemClock.uptimeMillis();
253 |
254 | if (mMovieStart == 0) {
255 | mMovieStart = now;
256 | }
257 |
258 | int dur = movie.duration();
259 |
260 | if (dur == 0) {
261 | dur = DEFAULT_MOVIE_VIEW_DURATION;
262 | }
263 |
264 | mCurrentAnimationTime = (int) ((now - mMovieStart) % dur);
265 | }
266 |
267 | /**
268 | * Draw current GIF frame
269 | */
270 | private void drawMovieFrame(Canvas canvas) {
271 |
272 | movie.setTime(mCurrentAnimationTime);
273 |
274 | canvas.save(Canvas.MATRIX_SAVE_FLAG);
275 | canvas.scale(mScale, mScale);
276 | movie.draw(canvas, mLeft / mScale, mTop / mScale);
277 | canvas.restore();
278 | }
279 |
280 | @SuppressLint("NewApi")
281 | @Override
282 | public void onScreenStateChanged(int screenState) {
283 | super.onScreenStateChanged(screenState);
284 | mVisible = screenState == SCREEN_STATE_ON;
285 | invalidateView();
286 | }
287 |
288 | @SuppressLint("NewApi")
289 | @Override
290 | protected void onVisibilityChanged(View changedView, int visibility) {
291 | super.onVisibilityChanged(changedView, visibility);
292 | mVisible = visibility == View.VISIBLE;
293 | invalidateView();
294 | }
295 |
296 | @Override
297 | protected void onWindowVisibilityChanged(int visibility) {
298 | super.onWindowVisibilityChanged(visibility);
299 | mVisible = visibility == View.VISIBLE;
300 | invalidateView();
301 | }
302 |
303 | private float dx, dy;
304 |
305 | @Override
306 | public boolean onTouch(View v, MotionEvent event) {
307 | switch (event.getAction()) {
308 | case MotionEvent.ACTION_DOWN:
309 | dx = v.getX() - event.getRawX();
310 | dy = v.getY() - event.getRawY();
311 | break;
312 | case MotionEvent.ACTION_MOVE:
313 | v.animate()
314 | .x(event.getRawX() + dx)
315 | .y(event.getRawY() + dy)
316 | .setDuration(0)
317 | .start();
318 | break;
319 | default:
320 | return false;
321 | }
322 | return true;
323 | }
324 | }
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/main/java/com/knef/stickerview/StickerView.java:
--------------------------------------------------------------------------------
1 | package com.knef.stickerview;
2 |
3 |
4 | import android.content.Context;
5 | import android.content.res.Resources;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.graphics.Rect;
10 | import android.util.AttributeSet;
11 | import android.util.DisplayMetrics;
12 | import android.util.Log;
13 | import android.view.Gravity;
14 | import android.view.MotionEvent;
15 | import android.view.View;
16 | import android.view.ViewConfiguration;
17 | import android.view.ViewGroup;
18 | import android.widget.FrameLayout;
19 | import android.widget.ImageView;
20 |
21 |
22 | public abstract class StickerView extends FrameLayout {
23 |
24 | private boolean enableBorder = true;
25 | private float durationStart;
26 | private float durationEnd;
27 | private int touchSlop;
28 | float downX = 0f, downY = 0f;
29 |
30 | protected abstract void updateXY();
31 |
32 | protected abstract void updateSize();
33 |
34 | private IStickerOperation iStickerOperation;
35 |
36 | public void setOnSelectListener(IStickerOperation iStickerOperation) {
37 | this.iStickerOperation = iStickerOperation;
38 | }
39 |
40 | public static final String TAG = StickerView.class.getSimpleName();
41 | private BorderView iv_border;
42 | private ImageView iv_scale;
43 | private ImageView iv_delete;
44 | private ImageView iv_flip;
45 |
46 | // For scalling
47 | private float this_orgX = -1, this_orgY = -1;
48 | private float scale_orgX = -1, scale_orgY = -1;
49 | private double scale_orgWidth = -1, scale_orgHeight = -1;
50 | // For rotating
51 | private float rotate_orgX = -1, rotate_orgY = -1, rotate_newX = -1, rotate_newY = -1;
52 | // For moving
53 | private float move_orgX = -1, move_orgY = -1;
54 |
55 | private double centerX, centerY;
56 |
57 | private final static int BUTTON_SIZE_DP = 30;
58 | private final static int SELF_SIZE_DP = 100;
59 |
60 | public StickerView(Context context) {
61 | super(context);
62 | init(context);
63 | }
64 |
65 | public StickerView(Context context, AttributeSet attrs) {
66 | super(context, attrs);
67 | init(context);
68 | }
69 |
70 | public StickerView(Context context, AttributeSet attrs, int defStyle) {
71 | super(context, attrs, defStyle);
72 | init(context);
73 | }
74 |
75 | private void init(Context context) {
76 | touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
77 | this.iv_border = new BorderView(context);
78 | this.iv_scale = new ImageView(context);
79 | this.iv_delete = new ImageView(context);
80 | this.iv_flip = new ImageView(context);
81 |
82 | this.iv_scale.setImageResource(R.drawable.zoominout);
83 | this.iv_delete.setImageResource(R.drawable.remove);
84 | this.iv_flip.setImageResource(R.drawable.flip);
85 |
86 | this.setTag("DraggableViewGroup");
87 | this.iv_border.setTag("iv_border");
88 | this.iv_scale.setTag("iv_scale");
89 | this.iv_delete.setTag("iv_delete");
90 | this.iv_flip.setTag("iv_flip");
91 |
92 | int margin = convertDpToPixel(BUTTON_SIZE_DP, getContext()) / 2;
93 | int size = convertDpToPixel(SELF_SIZE_DP, getContext());
94 |
95 | FrameLayout.LayoutParams this_params =
96 | new FrameLayout.LayoutParams(
97 | size,
98 | size
99 | );
100 | this_params.gravity = Gravity.CENTER;
101 |
102 | FrameLayout.LayoutParams iv_main_params =
103 | new FrameLayout.LayoutParams(
104 | ViewGroup.LayoutParams.MATCH_PARENT,
105 | ViewGroup.LayoutParams.MATCH_PARENT
106 | );
107 | iv_main_params.setMargins(margin, margin, margin, margin);
108 |
109 | FrameLayout.LayoutParams iv_border_params =
110 | new FrameLayout.LayoutParams(
111 | ViewGroup.LayoutParams.MATCH_PARENT,
112 | ViewGroup.LayoutParams.MATCH_PARENT
113 | );
114 | iv_border_params.setMargins(margin, margin, margin, margin);
115 |
116 | FrameLayout.LayoutParams iv_scale_params =
117 | new FrameLayout.LayoutParams(
118 | convertDpToPixel(BUTTON_SIZE_DP, getContext()),
119 | convertDpToPixel(BUTTON_SIZE_DP, getContext())
120 | );
121 | iv_scale_params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
122 |
123 | FrameLayout.LayoutParams iv_delete_params =
124 | new FrameLayout.LayoutParams(
125 | convertDpToPixel(BUTTON_SIZE_DP, getContext()),
126 | convertDpToPixel(BUTTON_SIZE_DP, getContext())
127 | );
128 | iv_delete_params.gravity = Gravity.TOP | Gravity.RIGHT;
129 |
130 | FrameLayout.LayoutParams iv_flip_params =
131 | new FrameLayout.LayoutParams(
132 | convertDpToPixel(BUTTON_SIZE_DP, getContext()),
133 | convertDpToPixel(BUTTON_SIZE_DP, getContext())
134 | );
135 | iv_flip_params.gravity = Gravity.TOP | Gravity.LEFT;
136 |
137 | this.setLayoutParams(this_params);
138 | this.addView(getMainView(), iv_main_params);
139 | this.addView(iv_border, iv_border_params);
140 | this.addView(iv_scale, iv_scale_params);
141 | this.addView(iv_delete, iv_delete_params);
142 | this.addView(iv_flip, iv_flip_params);
143 | this.setOnTouchListener(mTouchListener);
144 | this.iv_scale.setOnTouchListener(mTouchListener);
145 | this.iv_delete.setOnClickListener(new OnClickListener() {
146 | @Override
147 | public void onClick(View view) {
148 | if (StickerView.this.getParent() != null) {
149 | ViewGroup myCanvas = ((ViewGroup) StickerView.this.getParent());
150 | myCanvas.removeView(StickerView.this);
151 | if(iStickerOperation!=null){
152 | iStickerOperation.onDelete((String) getTag(R.id.sticker_tag));
153 | }
154 | }
155 | }
156 | });
157 | this.iv_flip.setOnClickListener(new View.OnClickListener() {
158 |
159 | @Override
160 | public void onClick(View view) {
161 | Log.v(TAG, "flip the view");
162 |
163 | View mainView = getMainView();
164 | mainView.setRotationY(mainView.getRotationY() == -180f ? 0f : -180f);
165 | mainView.invalidate();
166 | requestLayout();
167 | }
168 | });
169 | }
170 |
171 | public boolean isFlip() {
172 | return getMainView().getRotationY() == -180f;
173 | }
174 |
175 | protected abstract View getMainView();
176 |
177 | private OnTouchListener mTouchListener = new OnTouchListener() {
178 | @Override
179 | public boolean onTouch(View view, MotionEvent event) {
180 | if (enableBorder) {
181 | if (view.getTag().equals("DraggableViewGroup")) {
182 | switch (event.getAction()) {
183 | case MotionEvent.ACTION_DOWN:
184 | Log.v(TAG, "sticker view action down");
185 | move_orgX = event.getRawX();
186 | move_orgY = event.getRawY();
187 | break;
188 | case MotionEvent.ACTION_MOVE:
189 | Log.v(TAG, "sticker view action move");
190 | float offsetX = event.getRawX() - move_orgX;
191 | float offsetY = event.getRawY() - move_orgY;
192 | StickerView.this.setX(StickerView.this.getX() + offsetX);
193 | StickerView.this.setY(StickerView.this.getY() + offsetY);
194 | move_orgX = event.getRawX();
195 | move_orgY = event.getRawY();
196 | break;
197 | case MotionEvent.ACTION_UP:
198 | Log.v(TAG, "sticker view action up");
199 | Rect bounds = new Rect();
200 | getGlobalVisibleRect(bounds);
201 | updateXY();
202 | updateSize();
203 | break;
204 | }
205 | } else if (view.getTag().equals("iv_scale")) {
206 | switch (event.getAction()) {
207 | case MotionEvent.ACTION_DOWN:
208 | Log.v(TAG, "iv_scale action down");
209 |
210 | this_orgX = StickerView.this.getX();
211 | this_orgY = StickerView.this.getY();
212 |
213 | scale_orgX = event.getRawX();
214 | scale_orgY = event.getRawY();
215 | scale_orgWidth = StickerView.this.getLayoutParams().width;
216 | scale_orgHeight = StickerView.this.getLayoutParams().height;
217 |
218 | rotate_orgX = event.getRawX();
219 | rotate_orgY = event.getRawY();
220 |
221 | centerX = StickerView.this.getX() +
222 | ((View) StickerView.this.getParent()).getX() +
223 | (float) StickerView.this.getWidth() / 2;
224 |
225 |
226 | calculateCenterY();
227 |
228 | break;
229 | case MotionEvent.ACTION_MOVE:
230 | Log.v(TAG, "iv_scale action move");
231 |
232 | rotate_newX = event.getRawX();
233 | rotate_newY = event.getRawY();
234 |
235 | double angle_diff = Math.abs(
236 | Math.atan2(event.getRawY() - scale_orgY, event.getRawX() - scale_orgX)
237 | - Math.atan2(scale_orgY - centerY, scale_orgX - centerX)) * 180 / Math.PI;
238 |
239 | Log.v(TAG, "angle_diff: " + angle_diff);
240 |
241 | double length1 = getLength(centerX, centerY, scale_orgX, scale_orgY);
242 | double length2 = getLength(centerX, centerY, event.getRawX(), event.getRawY());
243 |
244 | int size = convertDpToPixel(SELF_SIZE_DP, getContext());
245 | if (length2 > length1
246 | && (angle_diff < 25 || Math.abs(angle_diff - 180) < 25)
247 | ) {
248 | //scale up
249 | double offsetX = Math.abs(event.getRawX() - scale_orgX);
250 | double offsetY = Math.abs(event.getRawY() - scale_orgY);
251 | double offset = Math.max(offsetX, offsetY);
252 | offset = Math.round(offset);
253 | StickerView.this.getLayoutParams().width += offset;
254 | StickerView.this.getLayoutParams().height += offset;
255 | onScaling(true);
256 | //DraggableViewGroup.this.setX((float) (getX() - offset / 2));
257 | //DraggableViewGroup.this.setY((float) (getY() - offset / 2));
258 | } else if (length2 < length1
259 | && (angle_diff < 25 || Math.abs(angle_diff - 180) < 25)
260 | && StickerView.this.getLayoutParams().width > size / 2
261 | && StickerView.this.getLayoutParams().height > size / 2) {
262 | //scale down
263 | double offsetX = Math.abs(event.getRawX() - scale_orgX);
264 | double offsetY = Math.abs(event.getRawY() - scale_orgY);
265 | double offset = Math.max(offsetX, offsetY);
266 | offset = Math.round(offset);
267 | StickerView.this.getLayoutParams().width -= offset;
268 | StickerView.this.getLayoutParams().height -= offset;
269 | onScaling(false);
270 | }
271 |
272 | //rotate
273 |
274 | // double angle = Math.atan2(event.getRawY() - centerY, event.getRawX() - centerX) * 180 / Math.PI;
275 | // Log.v(TAG, "log angle: " + angle);
276 | //
277 | // //setRotation((float) angle - 45);
278 | // setRotation((float) angle - 45);
279 | // Log.v(TAG, "getRotation(): " + getRotation());
280 | //
281 | // onRotating();
282 | //
283 | // rotate_orgX = rotate_newX;
284 | // rotate_orgY = rotate_newY;
285 | //
286 | // scale_orgX = event.getRawX();
287 | // scale_orgY = event.getRawY();
288 | //
289 | // postInvalidate();
290 | // requestLayout();
291 | break;
292 | case MotionEvent.ACTION_UP:
293 | Log.v(TAG, "iv_scale action up");
294 | updateSize();
295 | break;
296 | }
297 | }
298 | } else {
299 | //convert onTouch to onClick
300 | switch (event.getAction()) {
301 | case MotionEvent.ACTION_DOWN:
302 | downX = event.getX();
303 | downY = event.getY();
304 | break;
305 | case MotionEvent.ACTION_UP:
306 | if (Math.abs(event.getX() - downX) < touchSlop
307 | && Math.abs(event.getY() - downY) < touchSlop) {
308 | if (iStickerOperation != null)
309 | iStickerOperation.onSelect((String) getTag(R.id.sticker_tag));
310 | }
311 | break;
312 | }
313 | }
314 | return true;
315 | }
316 | };
317 |
318 | private void calculateCenterY() {
319 | //double statusBarHeight = Math.ceil(25 * getContext().getResources().getDisplayMetrics().density);
320 | int result = 0;
321 | int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
322 | if (resourceId > 0) {
323 | result = getResources().getDimensionPixelSize(resourceId);
324 | }
325 | double statusBarHeight = result;
326 | centerY = StickerView.this.getY() +
327 | ((View) StickerView.this.getParent()).getY() +
328 | statusBarHeight +
329 | (float) StickerView.this.getHeight() / 2;
330 | }
331 |
332 | @Override
333 | protected void onDraw(Canvas canvas) {
334 | super.onDraw(canvas);
335 | }
336 |
337 | public void setStickerTag(String stickerTag) {
338 | setTag(R.id.sticker_tag, stickerTag);
339 | }
340 |
341 | public String getStickerTag() {
342 | return (String) getTag(R.id.sticker_tag);
343 | }
344 |
345 | private double getLength(double x1, double y1, double x2, double y2) {
346 | return Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2));
347 | }
348 |
349 | private float[] getRelativePos(float absX, float absY) {
350 | Log.v("ken", "getRelativePos getX:" + ((View) this.getParent()).getX());
351 | Log.v("ken", "getRelativePos getY:" + ((View) this.getParent()).getY());
352 | float[] pos = new float[]{
353 | absX - ((View) this.getParent()).getX(),
354 | absY - ((View) this.getParent()).getY()
355 | };
356 | Log.v(TAG, "getRelativePos absY:" + absY);
357 | Log.v(TAG, "getRelativePos relativeY:" + pos[1]);
358 | return pos;
359 | }
360 |
361 | public void setControlItemsHidden(boolean isHidden) {
362 | if (isHidden) {
363 | iv_border.setVisibility(View.INVISIBLE);
364 | iv_scale.setVisibility(View.INVISIBLE);
365 | iv_delete.setVisibility(View.INVISIBLE);
366 | iv_flip.setVisibility(View.INVISIBLE);
367 | } else {
368 | iv_border.setVisibility(View.VISIBLE);
369 | iv_scale.setVisibility(View.VISIBLE);
370 | iv_delete.setVisibility(View.VISIBLE);
371 | iv_flip.setVisibility(View.VISIBLE);
372 | }
373 | }
374 |
375 | protected View getImageViewFlip() {
376 | return iv_flip;
377 | }
378 |
379 | protected void onScaling(boolean scaleUp) {
380 | }
381 |
382 | protected void onRotating() {
383 | }
384 |
385 | public void select(boolean b) {
386 | enableBorder = b;
387 | setControlItemsHidden(!b);
388 | invalidate();
389 | }
390 |
391 |
392 | public float getDurationStart() {
393 | return durationStart;
394 | }
395 |
396 | public float getDurationEnd() {
397 | return durationEnd;
398 | }
399 |
400 |
401 | public void setDuration(float startVal, float endVal) {
402 | durationStart = startVal;
403 | durationEnd = endVal;
404 | }
405 |
406 | public void setVisible(boolean b) {
407 | if(b){
408 | setVisibility(VISIBLE);
409 | }else{
410 | setVisibility(INVISIBLE);
411 | }
412 | }
413 |
414 | private class BorderView extends View {
415 |
416 | private Rect border = new Rect();
417 | private Paint borderPaint = new Paint();
418 |
419 | public BorderView(Context context) {
420 | super(context);
421 | }
422 |
423 | public BorderView(Context context, AttributeSet attrs) {
424 | super(context, attrs);
425 | }
426 |
427 | public BorderView(Context context, AttributeSet attrs, int defStyle) {
428 | super(context, attrs, defStyle);
429 | }
430 |
431 | @Override
432 | protected void onDraw(Canvas canvas) {
433 | super.onDraw(canvas);
434 | // Draw sticker border
435 |
436 | FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) this.getLayoutParams();
437 | Log.v(TAG, "params.leftMargin: " + params.leftMargin);
438 | border.left = (int) this.getLeft() - params.leftMargin;
439 | border.top = (int) this.getTop() - params.topMargin;
440 | border.right = (int) this.getRight() - params.rightMargin;
441 | border.bottom = (int) this.getBottom() - params.bottomMargin;
442 | borderPaint.setStrokeWidth(6);
443 | borderPaint.setColor(Color.WHITE);
444 | borderPaint.setStyle(Paint.Style.STROKE);
445 | canvas.drawRect(border, borderPaint);
446 |
447 | updateXY();
448 | updateSize();
449 | }
450 | }
451 |
452 | private static int convertDpToPixel(float dp, Context context) {
453 | Resources resources = context.getResources();
454 | DisplayMetrics metrics = resources.getDisplayMetrics();
455 | float px = dp * (metrics.densityDpi / 160f);
456 | return (int) px;
457 | }
458 | }
459 |
--------------------------------------------------------------------------------