├── .gitignore ├── .idea ├── codeStyleSettings.xml ├── compiler.xml ├── encodings.xml ├── gradle.xml ├── kotlinc.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── ctech │ │ └── reaction │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── Anger.json │ │ ├── Haha.json │ │ ├── Like.json │ │ ├── Love.json │ │ ├── Sorry.json │ │ ├── Wow.json │ │ └── option2b.json │ ├── java │ │ └── com │ │ │ └── ctech │ │ │ └── reaction │ │ │ ├── MainActivity.java │ │ │ ├── model │ │ │ ├── Feed.java │ │ │ └── Reaction.java │ │ │ ├── ui │ │ │ ├── FeedAdapter.java │ │ │ └── FeedViewHolder.java │ │ │ ├── util │ │ │ ├── Constants.java │ │ │ └── DisplayUtil.java │ │ │ └── widget │ │ │ ├── ArcTranslate.java │ │ │ ├── Emotion.java │ │ │ ├── ReactionView.java │ │ │ └── RoundedBoard.java │ └── res │ │ ├── anim │ │ ├── pop_in.xml │ │ └── pop_out.xml │ │ ├── drawable-hdpi │ │ ├── ic_avatar.jpg │ │ ├── ic_badge_like.png │ │ ├── ic_comment.png │ │ ├── ic_like.png │ │ ├── ic_more.png │ │ ├── ic_post.png │ │ └── ic_share.png │ │ ├── drawable │ │ ├── label_background.xml │ │ └── popup_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── item_feed.xml │ │ ├── view_label.xml │ │ └── view_reaction.xml │ │ ├── menu │ │ └── menu_main.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-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── integers.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── ctech │ └── reaction │ └── ExampleUnitTest.java ├── assets └── demo.gif ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keyframe ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ ├── assets │ │ └── sample_file │ ├── java │ │ └── com │ │ │ └── facebook │ │ │ └── keyframes │ │ │ └── SanityTests.java │ └── res │ │ └── drawable │ │ ├── test_0.png │ │ ├── test_1.png │ │ ├── test_2.png │ │ ├── test_3.png │ │ ├── test_4.png │ │ ├── test_5.png │ │ ├── test_6.png │ │ ├── test_7.png │ │ ├── test_8.png │ │ └── test_9.png │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── facebook │ │ └── keyframes │ │ ├── KFPath.java │ │ ├── KeyframesDirectionallyScalingDrawable.java │ │ ├── KeyframesDrawable.java │ │ ├── KeyframesDrawableAnimationCallback.java │ │ ├── KeyframesDrawableBuilder.java │ │ ├── deserializers │ │ ├── AbstractListDeserializer.java │ │ ├── CommonDeserializerHelper.java │ │ ├── KFAnimationDeserializer.java │ │ ├── KFAnimationFrameDeserializer.java │ │ ├── KFAnimationGroupDeserializer.java │ │ ├── KFColorFrameDeserializer.java │ │ ├── KFFeatureDeserializer.java │ │ ├── KFFeatureEffectDeserializer.java │ │ ├── KFFeatureFrameDeserializer.java │ │ ├── KFGradientColorDeserializer.java │ │ ├── KFGradientDeserializer.java │ │ └── KFImageDeserializer.java │ │ ├── model │ │ ├── HasKeyFrame.java │ │ ├── KFAnimation.java │ │ ├── KFAnimationFrame.java │ │ ├── KFAnimationGroup.java │ │ ├── KFColorFrame.java │ │ ├── KFFeature.java │ │ ├── KFFeatureEffect.java │ │ ├── KFFeatureFrame.java │ │ ├── KFGradient.java │ │ ├── KFGradientColor.java │ │ ├── KFImage.java │ │ └── keyframedmodels │ │ │ ├── KeyFrameAnimationHelper.java │ │ │ ├── KeyFramedAnchorPoint.java │ │ │ ├── KeyFramedGradient.java │ │ │ ├── KeyFramedMatrixAnimation.java │ │ │ ├── KeyFramedObject.java │ │ │ ├── KeyFramedOpacity.java │ │ │ ├── KeyFramedPath.java │ │ │ └── KeyFramedStrokeWidth.java │ │ └── util │ │ ├── AnimationGroupSort.java │ │ ├── AnimationHelper.java │ │ ├── ArgCheckUtil.java │ │ ├── KFPathInterpolator.java │ │ ├── ListHelper.java │ │ └── VectorCommand.java │ └── test │ └── java │ └── com │ └── facebook │ └── keyframes │ ├── KFPathTest.java │ ├── model │ └── ModelTests.java │ └── util │ └── VectorCommandTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/* 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 229 | 231 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 1.8 41 | 42 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FBReaction 2 | 3 | ## Facebook-like "Like with Reaction" demo for Android 4 | 5 | 6 | ![](https://github.com/dbof10/FBReaction/blob/master/assets/demo.gif) 7 | 8 | Just like Facebook's app: 9 | - Press on like button 10 | - Keep your finger down, swipe left or right 11 | - Release your finger 12 | 13 | ### License 14 | 15 | ```java 16 | Copyright 2017 Cuong Le 17 | 18 | Licensed under the Apache License, Version 2.0 (the "License"); 19 | you may not use this file except in compliance with the License. 20 | You may obtain a copy of the License at 21 | 22 | http://www.apache.org/licenses/LICENSE-2.0 23 | 24 | Unless required by applicable law or agreed to in writing, software 25 | distributed under the License is distributed on an "AS IS" BASIS, 26 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 27 | See the License for the specific language governing permissions and 28 | limitations under the License.` 29 | ``` 30 | ## Support project 31 | 32 | If you like the work **Facebook reaction** is doing please consider a small donation: 33 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | android { 3 | compileSdkVersion 27 4 | buildToolsVersion "27.0.1" 5 | defaultConfig { 6 | applicationId "com.ctech.reaction" 7 | minSdkVersion 21 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 24 | exclude group: 'com.android.support', module: 'support-annotations' 25 | }) 26 | 27 | compile 'com.android.support:appcompat-v7:27.0.2' 28 | compile 'com.android.support:design:27.0.2' 29 | compile 'io.reactivex:rxjava:1.3.3' 30 | compile 'io.reactivex:rxandroid:1.2.1' 31 | 32 | 33 | compile 'com.jakewharton:butterknife:8.8.1' 34 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' 35 | 36 | compile project(':keyframe') 37 | 38 | testCompile 'junit:junit:4.12' 39 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 40 | } 41 | -------------------------------------------------------------------------------- /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 /Users/admin/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/ctech/reaction/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction; 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) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.ctech.reaction", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/assets/Love.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LOVE", 3 | "key": 2, 4 | "frame_rate": 24, 5 | "animation_frame_count": 41, 6 | "animation_groups": [ 7 | { 8 | "group_id": 1, 9 | "group_name": "Master Animation", 10 | "animations": [ 11 | { 12 | "property": "SCALE", 13 | "key_values": [ 14 | { 15 | "start_frame": 0, 16 | "data": [ 17 | 100, 18 | 100 19 | ] 20 | }, 21 | { 22 | "start_frame": 4, 23 | "data": [ 24 | 97.1, 25 | 97.1 26 | ] 27 | }, 28 | { 29 | "start_frame": 14, 30 | "data": [ 31 | 111.3, 32 | 111.3 33 | ] 34 | }, 35 | { 36 | "start_frame": 24, 37 | "data": [ 38 | 98.2, 39 | 98.2 40 | ] 41 | }, 42 | { 43 | "start_frame": 28, 44 | "data": [ 45 | 101.15, 46 | 101.15 47 | ] 48 | }, 49 | { 50 | "start_frame": 32, 51 | "data": [ 52 | 100, 53 | 100 54 | ] 55 | }, 56 | { 57 | "start_frame": 40, 58 | "data": [ 59 | 100, 60 | 100 61 | ] 62 | } 63 | ], 64 | "timing_curves": [ 65 | [ 66 | [ 67 | 0.16666666500000002, 68 | 0 69 | ], 70 | [ 71 | 0.833333335, 72 | 1 73 | ] 74 | ], 75 | [ 76 | [ 77 | 0.16666666500000002, 78 | 0 79 | ], 80 | [ 81 | 0.5, 82 | 1 83 | ] 84 | ], 85 | [ 86 | [ 87 | 0.5, 88 | 0 89 | ], 90 | [ 91 | 0.833333335, 92 | 1 93 | ] 94 | ], 95 | [ 96 | [ 97 | 0.08333333333500001, 98 | 0 99 | ], 100 | [ 101 | 0.833333335, 102 | 1 103 | ] 104 | ], 105 | [ 106 | [ 107 | 0.08333333333500001, 108 | 0 109 | ], 110 | [ 111 | 0.833333335, 112 | 1 113 | ] 114 | ], 115 | [ 116 | [ 117 | 0.07908275266566, 118 | 0 119 | ], 120 | [ 121 | 0.858296121933008, 122 | 1 123 | ] 124 | ] 125 | ], 126 | "anchor": [ 127 | 88.5, 128 | 92.0807037353516 129 | ] 130 | } 131 | ] 132 | } 133 | ], 134 | "features": [ 135 | { 136 | "name": "BG", 137 | "fill_color": "#fff25268", 138 | "key_frames": [ 139 | { 140 | "start_frame": 0, 141 | "data": [ 142 | "M176.25,88.25", 143 | "C176.25,136.85,136.85,176.25,88.25,176.25", 144 | "C39.65,176.25,0.25,136.85,0.25,88.25", 145 | "C0.25,39.65,39.65,0.25,88.25,0.25", 146 | "C136.85,0.25,176.25,39.65,176.25,88.25" 147 | ] 148 | } 149 | ] 150 | }, 151 | { 152 | "name": "<3", 153 | "animation_group": 1, 154 | "fill_color": "#ffffffff", 155 | "key_frames": [ 156 | { 157 | "start_frame": 0, 158 | "data": [ 159 | "M115.53,45.66", 160 | "Q91.5,45.66,88.5,65.02", 161 | "Q85.5,45.66,61.47,45.66", 162 | "C38.36,45.66,31.62,69.23,34.43,81.84", 163 | "Q41.87,115.16,88.5,138.5", 164 | "Q135.13,115.16,142.57,81.84", 165 | "C145.38,69.23,138.64,45.66,115.53,45.66" 166 | ] 167 | } 168 | ] 169 | } 170 | ], 171 | "canvas_size": [ 172 | 177, 173 | 177 174 | ] 175 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ctech/reaction/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.View; 8 | import android.widget.FrameLayout; 9 | import android.widget.LinearLayout; 10 | import butterknife.BindView; 11 | import butterknife.ButterKnife; 12 | import butterknife.OnClick; 13 | import com.ctech.reaction.ui.FeedAdapter; 14 | import com.ctech.reaction.util.Constants; 15 | import com.ctech.reaction.widget.ReactionView; 16 | 17 | public class MainActivity extends AppCompatActivity { 18 | 19 | private static final String TAG = "MainActivity"; 20 | 21 | //@BindView(R.id.rvNewFeed) 22 | RecyclerView rvNewFeed; 23 | 24 | @BindView(R.id.btReaction) 25 | LinearLayout btReaction; 26 | 27 | @BindView(R.id.root) 28 | FrameLayout root; 29 | 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_main); 35 | ButterKnife.bind(this); 36 | 37 | } 38 | 39 | private boolean scaledDown; 40 | 41 | 42 | @OnClick(R.id.btReaction) 43 | public void showPopup(View v){ 44 | ReactionView rvl = new ReactionView(this); 45 | root.addView(rvl); 46 | } 47 | 48 | 49 | 50 | private void setUpRecyclerView() { 51 | FeedAdapter adapter = new FeedAdapter(Constants.feeds); 52 | rvNewFeed.setLayoutManager(new LinearLayoutManager(this)); 53 | rvNewFeed.setAdapter(adapter); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/ctech/reaction/model/Feed.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction.model; 2 | 3 | /** 4 | * Created by KenZira on 2/2/17. 5 | */ 6 | 7 | public class Feed { 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/ctech/reaction/model/Reaction.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction.model; 2 | 3 | /** 4 | * Created by KenZira on 2/1/17. 5 | */ 6 | 7 | public class Reaction { 8 | 9 | public String type; 10 | public String source; 11 | 12 | public Reaction(String type, String source) { 13 | this.type = type; 14 | this.source = source; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/ctech/reaction/ui/FeedAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction.ui; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import com.ctech.reaction.R; 8 | import com.ctech.reaction.model.Feed; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by KenZira on 2/2/17. 13 | */ 14 | 15 | public class FeedAdapter extends RecyclerView.Adapter { 16 | private List feeds; 17 | 18 | public FeedAdapter(List feeds) { 19 | this.feeds = feeds; 20 | } 21 | 22 | @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 23 | View v = LayoutInflater.from(parent.getContext()) 24 | .inflate(R.layout.item_feed, parent, false); 25 | return new FeedViewHolder(v); 26 | } 27 | 28 | @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 29 | 30 | } 31 | 32 | @Override public int getItemCount() { 33 | return feeds.size(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/ctech/reaction/ui/FeedViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction.ui; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.Gravity; 5 | import android.view.View; 6 | import android.view.WindowManager; 7 | import android.widget.ImageView; 8 | import android.widget.PopupWindow; 9 | import butterknife.BindView; 10 | import butterknife.ButterKnife; 11 | import butterknife.OnClick; 12 | import com.ctech.reaction.R; 13 | 14 | /** 15 | * Created by KenZira on 2/2/17. 16 | */ 17 | 18 | public class FeedViewHolder extends RecyclerView.ViewHolder { 19 | @BindView(R.id.btLike) 20 | View btLike; 21 | 22 | @BindView(R.id.btComment) 23 | View btComment; 24 | 25 | @BindView(R.id.btShare) 26 | View btShare; 27 | 28 | @BindView(R.id.llFeedInfo) 29 | View llFeedInfo; 30 | 31 | @BindView(R.id.ivBadgeLike) 32 | ImageView ivBadgeLike; 33 | 34 | private final PopupWindow popupWindow; 35 | 36 | public FeedViewHolder(View itemView) { 37 | super(itemView); 38 | ButterKnife.bind(this, itemView); 39 | 40 | // final PopupReactionView popupView = new PopupReactionView(itemView.getContext()); 41 | popupWindow = new PopupWindow( 42 | null, 43 | WindowManager.LayoutParams.WRAP_CONTENT, 44 | WindowManager.LayoutParams.WRAP_CONTENT); 45 | popupWindow.setAnimationStyle(R.style.PopupAnimation); 46 | } 47 | 48 | @OnClick(R.id.btLike) 49 | public void handleLikeClick() { 50 | if (popupWindow.isShowing()) { 51 | popupWindow.dismiss(); 52 | } else { 53 | popupWindow.showAtLocation(llFeedInfo, Gravity.TOP | Gravity.START, ivBadgeLike.getRight(), 54 | llFeedInfo.getBottom() ); 55 | } 56 | } 57 | 58 | @OnClick(R.id.btComment) 59 | public void handleCommentClick() { 60 | 61 | } 62 | 63 | @OnClick(R.id.btShare) 64 | public void handleShareClick() { 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/ctech/reaction/util/Constants.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction.util; 2 | 3 | import com.ctech.reaction.model.Feed; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | /** 8 | * Created by KenZira on 2/1/17. 9 | */ 10 | 11 | public class Constants { 12 | 13 | public static int HORIZONTAL_SPACING = DisplayUtil.dpToPx(4); 14 | 15 | public static int VERTICAL_SPACING = DisplayUtil.dpToPx(8); 16 | 17 | public static int HEIGHT_VIEW_REACTION = DisplayUtil.dpToPx(260); 18 | 19 | public static final int MAX_ALPHA = 255; 20 | 21 | public static final List feeds = Arrays.asList(new Feed(), new Feed()); 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/ctech/reaction/util/DisplayUtil.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction.util; 2 | 3 | import android.content.res.Resources; 4 | import android.util.DisplayMetrics; 5 | 6 | /** 7 | * Created by KenZira on 3/18/17. 8 | */ 9 | 10 | public class DisplayUtil { 11 | 12 | public static int dpToPx(int dp) { 13 | DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics(); 14 | return dp * (metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/ctech/reaction/widget/ArcTranslate.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction.widget; 2 | 3 | import android.graphics.Point; 4 | import android.graphics.PointF; 5 | import android.view.animation.Animation; 6 | import android.view.animation.Transformation; 7 | 8 | public class ArcTranslate extends Animation { 9 | 10 | private final float mFromXValue; 11 | private final float mToXValue; 12 | private final float mYValue; 13 | private final int mFromXType; 14 | private final int mToXType; 15 | private final int mYType; 16 | private PointF start; 17 | private PointF end; 18 | private PointF middle; 19 | 20 | public ArcTranslate(long duration, int fromXType, float fromXValue, 21 | int toXType, float toXValue, int yType, float yValue) { 22 | setDuration(duration); 23 | 24 | mFromXValue = fromXValue; 25 | mToXValue = toXValue; 26 | mYValue = yValue; 27 | 28 | mFromXType = fromXType; 29 | mToXType = toXType; 30 | mYType = yType; 31 | } 32 | 33 | /** 34 | * Calculate the position on a quadratic bezier curve given three points 35 | * and the percentage of time passed. 36 | * from http://en.wikipedia.org/wiki/B%C3%A9zier_curve 37 | * 38 | * @param interpolatedTime - the fraction of the duration that has passed where 0<=time<=1 39 | * @param p0 - a single dimension of the starting point 40 | * @param p1 - a single dimension of the middle point 41 | * @param p2 - a single dimension of the ending point 42 | */ 43 | private long calcBezier(float interpolatedTime, float p0, float p1, float p2) { 44 | return Math.round((Math.pow((1 - interpolatedTime), 2) * p0) 45 | + (2 * (1 - interpolatedTime) * interpolatedTime * p1) 46 | + (Math.pow(interpolatedTime, 2) * p2)); 47 | } 48 | 49 | @Override 50 | protected void applyTransformation(float interpolatedTime, Transformation t) { 51 | float dx = calcBezier(interpolatedTime, start.x, middle.x, end.x); 52 | float dy = calcBezier(interpolatedTime, start.y, middle.y, end.y); 53 | t.getMatrix().setTranslate(dx, dy); 54 | } 55 | 56 | @Override 57 | public void initialize(int width, int height, int parentWidth, int parentHeight) { 58 | super.initialize(width, height, parentWidth, parentHeight); 59 | float startX = resolveSize(mFromXType, mFromXValue, width, parentWidth); 60 | float endX = resolveSize(mToXType, mToXValue, width, parentWidth); 61 | float middleY = resolveSize(mYType, mYValue, width, parentWidth); 62 | float middleX = startX + ((endX - startX) / 2); 63 | start = new PointF( startX, 0); 64 | end = new PointF(endX, 0); 65 | middle = new PointF(middleX, middleY); 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ctech/reaction/widget/Emotion.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | import android.graphics.Rect; 9 | import android.graphics.RectF; 10 | import android.util.Log; 11 | import android.view.LayoutInflater; 12 | import android.widget.TextView; 13 | import com.ctech.reaction.R; 14 | import com.ctech.reaction.util.Constants; 15 | import com.ctech.reaction.util.DisplayUtil; 16 | import com.facebook.keyframes.KeyframesDirectionallyScalingDrawable; 17 | import com.facebook.keyframes.KeyframesDrawable; 18 | import com.facebook.keyframes.KeyframesDrawableBuilder; 19 | import com.facebook.keyframes.deserializers.KFImageDeserializer; 20 | import com.facebook.keyframes.model.KFImage; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | 24 | /** 25 | * Created by KenZira on 3/10/17. 26 | */ 27 | 28 | public class Emotion { 29 | 30 | static final int SMALL_SIZE = DisplayUtil.dpToPx(24); 31 | static final int MEDIUM_SIZE = DisplayUtil.dpToPx(32); 32 | static final int LARGE_SIZE = DisplayUtil.dpToPx(72); 33 | 34 | private static final int SPACING_TO_LABEL = DisplayUtil.dpToPx(16); 35 | private static final int MAX_WIDTH_TITLE = DisplayUtil.dpToPx(56); 36 | 37 | int size = 0; 38 | 39 | int startAnimatedSize; 40 | int endAnimatedSize; 41 | 42 | float x; 43 | float y; 44 | 45 | float startAnimatedX; 46 | 47 | float startAnimatedY; 48 | float endAnimatedY; 49 | 50 | private Paint textPaint; 51 | 52 | private KeyframesDrawable imageDrawable; 53 | 54 | private Rect imageBound; 55 | private RectF textBound; 56 | 57 | private Context context; 58 | private float labelRatio; 59 | private Bitmap imageTitle; 60 | 61 | Emotion(Context context, String title, String imageResource) { 62 | this.context = context; 63 | 64 | imageDrawable = new KeyframesDrawableBuilder().withImage(getKFImage(imageResource)).build(); 65 | imageDrawable.startAnimation(); 66 | 67 | textPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 68 | 69 | imageBound = new Rect(); 70 | textBound = new RectF(); 71 | 72 | snapShotLabelView(title); 73 | } 74 | 75 | private KFImage getKFImage(String fileName) { 76 | AssetManager assetManager = context.getAssets(); 77 | 78 | InputStream stream; 79 | KFImage kfImage = null; 80 | 81 | try { 82 | stream = assetManager.open(fileName); 83 | kfImage = KFImageDeserializer.deserialize(stream); 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | } 87 | 88 | return kfImage; 89 | } 90 | 91 | private void snapShotLabelView(String title) { 92 | LayoutInflater inflater = LayoutInflater.from(context); 93 | TextView labelView = (TextView) inflater.inflate(R.layout.view_label, null, false); 94 | labelView.setText(title); 95 | 96 | int width = (int) context.getResources().getDimension(R.dimen.label_width); 97 | int height = (int) context.getResources().getDimension(R.dimen.label_height); 98 | 99 | labelRatio = width / height ; 100 | imageTitle = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444); 101 | 102 | Canvas canvas = new Canvas(imageTitle); 103 | labelView.layout(0, 0, width, height); 104 | labelView.draw(canvas); 105 | } 106 | 107 | private void setAlphaTitle(int alpha) { 108 | textPaint.setAlpha(alpha); 109 | } 110 | 111 | void draw(final Canvas canvas) { 112 | imageBound.set((int)x,(int) y, (int)x + size, (int)y + size); 113 | imageDrawable.setBounds(imageBound); 114 | imageDrawable.draw(canvas); 115 | drawLabel(canvas); 116 | } 117 | 118 | private void drawLabel(Canvas canvas) { 119 | int width = size - MEDIUM_SIZE ; 120 | int height = (int) (width / labelRatio); 121 | 122 | if (width <= 0) return; 123 | 124 | setAlphaTitle(Constants.MAX_ALPHA * width / MAX_WIDTH_TITLE); 125 | 126 | float x = this.x + (size - width) / 2; 127 | float y = this.y - SPACING_TO_LABEL - height; 128 | 129 | textBound.set(x, y, x + width, y + height); 130 | canvas.drawBitmap(imageTitle, null, textBound, textPaint); 131 | } 132 | 133 | void setCurrentSize(int currentSize) { 134 | if(currentSize > this.size){ 135 | imageDrawable.setDirectionalScale(0.5F, 0.5F, 136 | KeyframesDirectionallyScalingDrawable.ScaleDirection.DOWN); 137 | }else { 138 | imageDrawable.setDirectionalScale(0.5F, 0.5F, 139 | KeyframesDirectionallyScalingDrawable.ScaleDirection.UP); 140 | } 141 | this.size = currentSize; 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/com/ctech/reaction/widget/ReactionView.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.util.AttributeSet; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | import android.view.animation.Animation; 10 | import android.view.animation.Transformation; 11 | import com.ctech.reaction.util.Constants; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | /** 16 | * Created by KenZira on 3/10/17. 17 | */ 18 | 19 | public class ReactionView extends View { 20 | 21 | private static final int SCALE_DURATION = 200; 22 | private static final int TRANSLATION_DURATION = 800; 23 | private static final int CHILD_TRANSLATION_DURATION = 300; 24 | private static final int CHILD_DELAY = 100; 25 | private static final int DRAW_DELAY = 50; 26 | private RoundedBoard board; 27 | private List emotions; 28 | private int selectedIndex = -1; 29 | 30 | private SelectingAnimation selectingAnimation; 31 | private DeselectAnimation deselectAnimation; 32 | 33 | private Runnable runnable = new Runnable() { 34 | @Override public void run() { 35 | invalidate(); 36 | } 37 | }; 38 | 39 | public ReactionView(Context context) { 40 | super(context); 41 | init(); 42 | } 43 | 44 | public ReactionView(Context context, AttributeSet attrs) { 45 | super(context, attrs); 46 | init(); 47 | } 48 | 49 | public ReactionView(Context context, AttributeSet attrs, int defStyleAttr) { 50 | super(context, attrs, defStyleAttr); 51 | init(); 52 | } 53 | 54 | private void init() { 55 | board = new RoundedBoard(); 56 | setLayerType(LAYER_TYPE_SOFTWARE, null); 57 | 58 | emotions = Arrays.asList( 59 | new Emotion(getContext(), "Like", "Like.json"), 60 | new Emotion(getContext(), "Love", "Love.json"), 61 | new Emotion(getContext(), "Haha", "Haha.json"), 62 | new Emotion(getContext(), "Wow", "Wow.json"), 63 | new Emotion(getContext(), "Sorry", "Sorry.json"), 64 | new Emotion(getContext(), "Angry", "Anger.json") 65 | ); 66 | 67 | selectingAnimation = new SelectingAnimation(); 68 | deselectAnimation = new DeselectAnimation(); 69 | deselectAnimation.setAnimationListener(new Animation.AnimationListener() { 70 | @Override public void onAnimationStart(Animation animation) {} 71 | 72 | @Override public void onAnimationEnd(Animation animation) { 73 | selectedIndex = -1; 74 | } 75 | @Override public void onAnimationRepeat(Animation animation) {} 76 | }); 77 | 78 | startAnimation(new TranslationAnimation()); 79 | } 80 | 81 | @Override 82 | public boolean onTouchEvent(MotionEvent event) { 83 | switch (event.getAction()) { 84 | case MotionEvent.ACTION_DOWN: 85 | return true; 86 | case MotionEvent.ACTION_MOVE: 87 | for (int i = 0; i < emotions.size(); i++) { 88 | if (event.getX() > emotions.get(i).x && 89 | event.getX() < emotions.get(i).x + emotions.get(i).size) { 90 | onSelect(i); 91 | break; 92 | } 93 | } 94 | return true; 95 | case MotionEvent.ACTION_UP: 96 | onDeselect(); 97 | return true; 98 | } 99 | return super.onTouchEvent(event); 100 | } 101 | 102 | private void onSelect(int index) { 103 | if (selectedIndex == index) { 104 | return; 105 | } 106 | 107 | selectedIndex = index; 108 | 109 | selectingAnimation.prepare(); 110 | startAnimation(selectingAnimation); 111 | } 112 | 113 | private void onDeselect() { 114 | deselectAnimation.prepare(); 115 | startAnimation(deselectAnimation); 116 | } 117 | 118 | @Override 119 | protected void onDraw(final Canvas canvas) { 120 | board.draw(canvas); 121 | for (Emotion emotion : emotions) { 122 | emotion.draw(canvas); 123 | } 124 | 125 | postDelayed(runnable, DRAW_DELAY); 126 | } 127 | 128 | private void animateEmotions(float interpolatedTime) { 129 | 130 | for (Emotion emotion : emotions) { 131 | animateEmotionSize(emotion, interpolatedTime); 132 | animateEmotionPosition(emotion); 133 | } 134 | } 135 | 136 | private void animateEmotionPosition(Emotion emotion) { 137 | emotion.y = RoundedBoard.BASE_LINE - emotion.size; 138 | 139 | emotions.get(0).x = RoundedBoard.LEFT + Constants.HORIZONTAL_SPACING; 140 | emotions.get(emotions.size() - 1).x = 141 | RoundedBoard.LEFT + RoundedBoard.WIDTH - Constants.HORIZONTAL_SPACING 142 | - emotions.get(emotions.size() - 1).size; 143 | 144 | for (int i = 1; i < selectedIndex; i++) { 145 | emotions.get(i).x = emotions.get(i - 1).x + emotions.get(i - 1).size 146 | + Constants.HORIZONTAL_SPACING; 147 | } 148 | 149 | for (int i = emotions.size() - 2; i > selectedIndex; i--) { 150 | emotions.get(i).x = emotions.get(i + 1).x - emotions.get(i).size 151 | - Constants.HORIZONTAL_SPACING; 152 | } 153 | 154 | if (selectedIndex > 0) { 155 | emotions.get(selectedIndex).x = emotions.get(selectedIndex - 1).x 156 | + emotions.get(selectedIndex - 1).size + Constants.HORIZONTAL_SPACING; 157 | } 158 | } 159 | 160 | private void animateEmotionSize(Emotion emotion, float interpolatedTime) { 161 | emotion.setCurrentSize(emotion.startAnimatedSize + 162 | (int) (interpolatedTime * (emotion.endAnimatedSize - emotion.startAnimatedSize))); 163 | } 164 | 165 | private void animateRoundedBoard(float interpolatedTime) { 166 | board.setCurrentHeight(board.startAnimatedHeight + (interpolatedTime * 167 | (board.endAnimatedHeight - board.startAnimatedHeight))); 168 | } 169 | 170 | private class SelectingAnimation extends Animation { 171 | 172 | SelectingAnimation() { 173 | setDuration(SCALE_DURATION); 174 | } 175 | 176 | void prepare(){ 177 | prepareEmotions(); 178 | prepareRoundedBoard(); 179 | } 180 | 181 | private void prepareEmotions() { 182 | for (int i = 0; i < emotions.size(); i++) { 183 | emotions.get(i).startAnimatedSize = emotions.get(i).size; 184 | 185 | if (i == selectedIndex) { 186 | emotions.get(i).endAnimatedSize = Emotion.LARGE_SIZE; 187 | } else { 188 | emotions.get(i).endAnimatedSize = Emotion.SMALL_SIZE; 189 | } 190 | } 191 | } 192 | 193 | private void prepareRoundedBoard() { 194 | board.startAnimatedHeight = board.height; 195 | board.endAnimatedHeight = RoundedBoard.SCALED_DOWN_HEIGHT; 196 | } 197 | 198 | @Override 199 | protected void applyTransformation(float interpolatedTime, Transformation t) { 200 | animateRoundedBoard(interpolatedTime); 201 | animateEmotions(interpolatedTime); 202 | } 203 | } 204 | 205 | private class DeselectAnimation extends Animation { 206 | 207 | DeselectAnimation() { 208 | setDuration(SCALE_DURATION); 209 | } 210 | 211 | void prepare(){ 212 | prepareRoundedBoard(); 213 | prepareEmotions(); 214 | } 215 | 216 | private void prepareEmotions() { 217 | for (Emotion emotion : emotions) { 218 | emotion.startAnimatedSize = emotion.size; 219 | emotion.endAnimatedSize = Emotion.MEDIUM_SIZE; 220 | } 221 | } 222 | 223 | private void prepareRoundedBoard() { 224 | board.startAnimatedHeight = board.height; 225 | board.endAnimatedHeight = RoundedBoard.HEIGHT; 226 | } 227 | 228 | @Override 229 | protected void applyTransformation(float interpolatedTime, Transformation t) { 230 | animateRoundedBoard(interpolatedTime); 231 | animateEmotions(interpolatedTime); 232 | } 233 | } 234 | 235 | private class TranslationAnimation extends Animation { 236 | 237 | private static final int TRANSLATION_DISTANCE = 150; 238 | private final int EMOTION_RADIUS = Emotion.MEDIUM_SIZE / 2; 239 | 240 | TranslationAnimation() { 241 | setDuration(TRANSLATION_DURATION); 242 | prepareRoundedBoard(); 243 | prepareEmotions(); 244 | } 245 | 246 | private void prepareEmotions() { 247 | for (int i = 0; i < emotions.size(); i++) { 248 | emotions.get(i).endAnimatedY = RoundedBoard.TOP + Constants.VERTICAL_SPACING; 249 | emotions.get(i).startAnimatedY = 250 | emotions.get(i).y = RoundedBoard.BOTTOM + TRANSLATION_DISTANCE; 251 | 252 | emotions.get(i).startAnimatedX 253 | = emotions.get(i).x = i == 0 ? RoundedBoard.LEFT 254 | + Constants.HORIZONTAL_SPACING + (Emotion.MEDIUM_SIZE / 2) 255 | : emotions.get(i - 1).x + Emotion.MEDIUM_SIZE + Constants.HORIZONTAL_SPACING; 256 | } 257 | } 258 | 259 | private void prepareRoundedBoard() { 260 | board.startAnimatedY = board.y = RoundedBoard.BOTTOM + TRANSLATION_DISTANCE; 261 | board.endAnimatedY = RoundedBoard.TOP; 262 | } 263 | 264 | @Override 265 | protected void applyTransformation(float interpolatedTime, Transformation t) { 266 | translateEmotions(interpolatedTime); 267 | translateRoundedBoard(); 268 | } 269 | 270 | private void translateEmotions(float interpolatedTime) { 271 | float currentTime = interpolatedTime * TRANSLATION_DURATION; 272 | for (int i = 0; i < emotions.size(); i++) { 273 | 274 | int delayOfChild = CHILD_DELAY * i; 275 | 276 | Emotion view = emotions.get(i); 277 | if ((currentTime > delayOfChild)) { 278 | if ((currentTime - delayOfChild) <= CHILD_TRANSLATION_DURATION) { 279 | 280 | float progressOfChild = ((currentTime - delayOfChild) / CHILD_TRANSLATION_DURATION); 281 | view.y = view.startAnimatedY + 282 | progressOfChild * (view.endAnimatedY - view.startAnimatedY); 283 | 284 | view.x = view.startAnimatedX - progressOfChild * EMOTION_RADIUS; 285 | 286 | view.setCurrentSize((int) (progressOfChild * Emotion.MEDIUM_SIZE)); 287 | } else { 288 | view.x = view.startAnimatedX - EMOTION_RADIUS; 289 | view.y = view.endAnimatedY; 290 | view.setCurrentSize(Emotion.MEDIUM_SIZE); 291 | } 292 | } 293 | } 294 | } 295 | 296 | private void translateRoundedBoard() { 297 | Emotion firstEmoticon = emotions.get(0); 298 | float d = 299 | (firstEmoticon.y - firstEmoticon.startAnimatedY) / (firstEmoticon.endAnimatedY 300 | - firstEmoticon.startAnimatedY) * (board.endAnimatedY - board.startAnimatedY); 301 | 302 | board.y = board.startAnimatedY + d; 303 | } 304 | 305 | } 306 | 307 | } 308 | -------------------------------------------------------------------------------- /app/src/main/java/com/ctech/reaction/widget/RoundedBoard.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction.widget; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.graphics.RectF; 7 | import com.ctech.reaction.util.Constants; 8 | import com.ctech.reaction.util.DisplayUtil; 9 | 10 | /** 11 | * Created by KenZira on 3/10/17. 12 | */ 13 | 14 | public class RoundedBoard { 15 | 16 | static final int WIDTH = 6 * Emotion.MEDIUM_SIZE + 7 * Constants.HORIZONTAL_SPACING; 17 | 18 | static final int HEIGHT = DisplayUtil.dpToPx(48); 19 | 20 | static final int SCALED_DOWN_HEIGHT = DisplayUtil.dpToPx(38); 21 | 22 | static final float LEFT = DisplayUtil.dpToPx(16); 23 | 24 | static final float BOTTOM = Constants.HEIGHT_VIEW_REACTION - 200; 25 | 26 | static final float TOP = BOTTOM - HEIGHT; 27 | 28 | static final float BASE_LINE = TOP + Emotion.MEDIUM_SIZE + Constants.VERTICAL_SPACING; 29 | 30 | float height = HEIGHT; 31 | float y; 32 | 33 | private float radius = height / 2; 34 | 35 | float startAnimatedHeight; 36 | float endAnimatedHeight; 37 | 38 | float startAnimatedY; 39 | float endAnimatedY; 40 | 41 | private Paint boardPaint; 42 | private RectF rect; 43 | 44 | RoundedBoard() { 45 | initPaint(); 46 | rect = new RectF(); 47 | } 48 | 49 | private void initPaint() { 50 | boardPaint = new Paint(); 51 | boardPaint.setAntiAlias(true); 52 | boardPaint.setStyle(Paint.Style.FILL); 53 | boardPaint.setColor(Color.WHITE); 54 | boardPaint.setShadowLayer(5.0f, 0.0f, 2.0f, 0xFF000000); 55 | } 56 | 57 | void setCurrentHeight(float newHeight) { 58 | height = newHeight; 59 | y = BOTTOM - height; 60 | } 61 | 62 | void draw(Canvas canvas) { 63 | rect.set(LEFT, y, LEFT + WIDTH, y + height); 64 | canvas.drawRoundRect(rect, radius, radius, boardPaint); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/res/anim/pop_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 11 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/anim/pop_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/drawable-hdpi/ic_avatar.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_badge_like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/drawable-hdpi/ic_badge_like.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/drawable-hdpi/ic_comment.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/drawable-hdpi/ic_like.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/drawable-hdpi/ic_more.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/drawable-hdpi/ic_post.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/drawable-hdpi/ic_share.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/label_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/popup_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 19 | 20 | 24 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_feed.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 20 | 32 | 41 | 52 | 53 | 55 | 56 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 81 | 82 | 91 | 92 | 97 | 104 | 105 | 106 | 115 | 116 | 121 | 128 | 129 | 130 | 139 | 140 | 145 | 152 | 153 | 154 | 155 | 156 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_label.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_reaction.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 25 | 26 | 37 | 38 | 46 | 47 | 54 | 55 | 56 | 62 | 63 | 64 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #dcdee3 7 | #FFFFFF 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 56dp 7 | 24dp 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 150 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Reaction 3 | Settings 4 | Nguyễn Thùy Ý Nhi 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 17 | 18 | 29 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/test/java/com/ctech/reaction/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.ctech.reaction; 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 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/assets/demo.gif -------------------------------------------------------------------------------- /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 | google() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.0.1' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | google() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Dec 17 20:59:21 ICT 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-4.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 | -------------------------------------------------------------------------------- /keyframe/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /keyframe/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 27 5 | buildToolsVersion '27.0.1' 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 27 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | testOptions { 20 | unitTests.returnDefaultValues = true 21 | } 22 | } 23 | 24 | dependencies { 25 | testCompile 'junit:junit:4.12' 26 | testCompile 'org.mockito:mockito-core:1.10.19' 27 | } 28 | 29 | 30 | task createJar(type: Copy) { 31 | from('build/intermediates/bundles/release') 32 | into('libs/') 33 | include('classes.jar') 34 | // rename('classes.jar', 'keyframes-' + VERSION_NAME + '.jar') 35 | } 36 | -------------------------------------------------------------------------------- /keyframe/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/markpeng/Desktop/stuff/android-sdk/android-sdk-macosx/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 | -------------------------------------------------------------------------------- /keyframe/src/androidTest/java/com/facebook/keyframes/SanityTests.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes; 11 | 12 | import android.graphics.Bitmap; 13 | import android.graphics.BitmapFactory; 14 | import android.graphics.Canvas; 15 | import android.graphics.Color; 16 | import android.graphics.PorterDuff; 17 | import android.graphics.drawable.BitmapDrawable; 18 | import android.test.AndroidTestCase; 19 | 20 | import com.facebook.keyframes.model.KFImage; 21 | import com.facebook.keyframes.deserializers.KFImageDeserializer; 22 | 23 | import junit.framework.Assert; 24 | 25 | import java.io.InputStream; 26 | 27 | public class SanityTests extends AndroidTestCase { 28 | 29 | private static final int TEST_CANVAS_SIZE_PX = 500; 30 | 31 | /** 32 | * Sanity deserializer test to make sure a valid file deserializes into a proper object. 33 | */ 34 | public void testDeserializeValidFile() throws Exception { 35 | InputStream stream = getContext().getResources().getAssets().open("sample_file"); 36 | KFImage deserializedModel = KFImageDeserializer.deserialize(stream); 37 | Assert.assertNotNull(deserializedModel); 38 | } 39 | 40 | /** 41 | * Sanity rendering test that renders the sample image and checks frames at 10% progress 42 | * increments, comparing images with a pre-generated set. 43 | */ 44 | public void testFramesForSample() throws Exception { 45 | InputStream stream = getContext().getResources().getAssets().open("sample_file"); 46 | KFImage kfImage = KFImageDeserializer.deserialize(stream); 47 | Bitmap testBitmap = 48 | Bitmap.createBitmap(TEST_CANVAS_SIZE_PX, TEST_CANVAS_SIZE_PX, Bitmap.Config.ARGB_8888); 49 | Canvas canvas = new Canvas(testBitmap); 50 | 51 | KeyframesDrawable testDrawable = new KeyframesDrawableBuilder().withImage(kfImage).build(); 52 | testDrawable.setBounds(0, 0, TEST_CANVAS_SIZE_PX, TEST_CANVAS_SIZE_PX); 53 | 54 | float step = .1f; 55 | for (float progress = 0; progress <= 1; progress += step) { 56 | int resIdentifier = 57 | getContext().getResources().getIdentifier( 58 | "test_" + (int) (progress / step), 59 | "drawable", 60 | getContext().getPackageName()); 61 | Bitmap compareBitmap = 62 | BitmapFactory.decodeStream(getContext().getResources().openRawResource(resIdentifier)); 63 | 64 | testDrawable.setFrameProgress(kfImage.getFrameCount() * progress); 65 | canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); 66 | testDrawable.draw(canvas); 67 | Assert.assertTrue(testBitmap.sameAs(compareBitmap)); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /keyframe/src/androidTest/res/drawable/test_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/keyframe/src/androidTest/res/drawable/test_0.png -------------------------------------------------------------------------------- /keyframe/src/androidTest/res/drawable/test_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/keyframe/src/androidTest/res/drawable/test_1.png -------------------------------------------------------------------------------- /keyframe/src/androidTest/res/drawable/test_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/keyframe/src/androidTest/res/drawable/test_2.png -------------------------------------------------------------------------------- /keyframe/src/androidTest/res/drawable/test_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/keyframe/src/androidTest/res/drawable/test_3.png -------------------------------------------------------------------------------- /keyframe/src/androidTest/res/drawable/test_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/keyframe/src/androidTest/res/drawable/test_4.png -------------------------------------------------------------------------------- /keyframe/src/androidTest/res/drawable/test_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/keyframe/src/androidTest/res/drawable/test_5.png -------------------------------------------------------------------------------- /keyframe/src/androidTest/res/drawable/test_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/keyframe/src/androidTest/res/drawable/test_6.png -------------------------------------------------------------------------------- /keyframe/src/androidTest/res/drawable/test_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/keyframe/src/androidTest/res/drawable/test_7.png -------------------------------------------------------------------------------- /keyframe/src/androidTest/res/drawable/test_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/keyframe/src/androidTest/res/drawable/test_8.png -------------------------------------------------------------------------------- /keyframe/src/androidTest/res/drawable/test_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbof10/FBReaction/f69640ae879b82449b251c3c78b45ac3e0348776/keyframe/src/androidTest/res/drawable/test_9.png -------------------------------------------------------------------------------- /keyframe/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/KFPath.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes; 11 | 12 | import android.graphics.Matrix; 13 | import android.graphics.Path; 14 | 15 | /** 16 | * A wrapper around the Android {@link android.graphics.Path} object, only revealing methods which 17 | * we will use in Keyframes, as well as some basic ability to keep track of the last point set. 18 | */ 19 | public class KFPath { 20 | 21 | private final Path mPath; 22 | private final float[] mLastPoint; 23 | 24 | public KFPath() { 25 | this(new Path(), new float[]{0, 0}); 26 | } 27 | 28 | /** 29 | * Constructor for tests to pass in objects 30 | */ 31 | KFPath(Path path, float[] lastPoint) { 32 | mPath = path; 33 | mLastPoint = lastPoint; 34 | } 35 | 36 | /** 37 | * Returns the last point, which should *never* be modified outside of this class. 38 | */ 39 | public float[] getLastPoint() { 40 | return mLastPoint; 41 | } 42 | 43 | /** 44 | * See {@link Path#reset()}. 45 | */ 46 | public void reset() { 47 | mPath.reset(); 48 | adjustLastPoint(0, 0); 49 | } 50 | 51 | /** 52 | * See {@link Path#moveTo(float, float)}. 53 | */ 54 | public void moveTo(float x, float y) { 55 | mPath.moveTo(x, y); 56 | adjustLastPoint(x, y); 57 | } 58 | 59 | /** 60 | * See {@link Path#rMoveTo(float, float)}. 61 | */ 62 | public void rMoveTo(float dx, float dy) { 63 | mPath.rMoveTo(dx, dy); 64 | rAdjustLastPoint(dx, dy); 65 | } 66 | 67 | /** 68 | * See {@link Path#lineTo(float, float)}. 69 | */ 70 | public void lineTo(float x, float y) { 71 | mPath.lineTo(x, y); 72 | adjustLastPoint(x, y); 73 | } 74 | 75 | /** 76 | * See {@link Path#rLineTo(float, float)}. 77 | */ 78 | public void rLineTo(float dx, float dy) { 79 | mPath.rLineTo(dx, dy); 80 | rAdjustLastPoint(dx, dy); 81 | } 82 | 83 | /** 84 | * See {@link Path#quadTo(float, float, float, float)}. 85 | */ 86 | public void quadTo(float x1, float y1, float x2, float y2) { 87 | mPath.quadTo(x1, y1, x2, y2); 88 | adjustLastPoint(x2, y2); 89 | } 90 | 91 | /** 92 | * See {@link Path#rQuadTo(float, float, float, float)}. 93 | */ 94 | public void rQuadTo(float dx1, float dy1, float dx2, float dy2) { 95 | mPath.rQuadTo(dx1, dy1, dx2, dy2); 96 | rAdjustLastPoint(dx2, dy2); 97 | } 98 | 99 | /** 100 | * See {@link Path#cubicTo(float, float, float, float, float, float)}. 101 | */ 102 | public void cubicTo( 103 | float x1, 104 | float y1, 105 | float x2, 106 | float y2, 107 | float x3, 108 | float y3) { 109 | mPath.cubicTo(x1, y1, x2, y2, x3, y3); 110 | adjustLastPoint(x3, y3); 111 | } 112 | 113 | /** 114 | * See {@link Path#rCubicTo(float, float, float, float, float, float)}. 115 | */ 116 | public void rCubicTo( 117 | float dx1, 118 | float dy1, 119 | float dx2, 120 | float dy2, 121 | float dx3, 122 | float dy3) { 123 | mPath.rCubicTo(dx1, dy1, dx2, dy2, dx3, dy3); 124 | rAdjustLastPoint(dx3, dy3); 125 | } 126 | 127 | /** 128 | * See {@link Path#transform(Matrix)}. 129 | */ 130 | public void transform(Matrix matrix) { 131 | mPath.transform(matrix); 132 | matrix.mapPoints(mLastPoint); 133 | } 134 | 135 | /** 136 | * See {@link Path#isEmpty()}. 137 | */ 138 | public boolean isEmpty() { 139 | return mPath.isEmpty(); 140 | } 141 | 142 | /** 143 | * Returns this path, which should *never* be modified outside of this class. 144 | */ 145 | protected Path getPath() { 146 | return mPath; 147 | } 148 | 149 | /** 150 | * Moves mLastPoint to (x, y). 151 | */ 152 | private void adjustLastPoint(float x, float y) { 153 | mLastPoint[0] = x; 154 | mLastPoint[1] = y; 155 | } 156 | 157 | /** 158 | * Moves mLastPoint by (+dx, +dy). 159 | */ 160 | private void rAdjustLastPoint(float dx, float dy) { 161 | mLastPoint[0] += dx; 162 | mLastPoint[1] += dy; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/KeyframesDirectionallyScalingDrawable.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes; 11 | 12 | public interface KeyframesDirectionallyScalingDrawable { 13 | 14 | /** 15 | * The direction to scale an image from the end. If direction UP is used, the origin of the 16 | * scaling will be the bottom edge of the image. Likewise, if DOWN is used, the origin will be 17 | * the top edge of the image. 18 | */ 19 | enum ScaleDirection { 20 | UP, 21 | DOWN 22 | } 23 | 24 | /** 25 | * Sets special directional scale for the drawable. This can cause the drawable to draw outside 26 | * of the initially set bounds, but retains image quality when scaling up/down and allows 27 | * fractional scaling not bound by integers. 28 | * @param scaleFromCenter Scale value from the center of the image 29 | * @param scaleFromEnd Scale value from the end of the image, either the bottom or top. 30 | * @param direction The {@link ScaleDirection} to scale towards, used for scaleFromEnd param 31 | */ 32 | void setDirectionalScale(float scaleFromCenter, float scaleFromEnd, ScaleDirection direction); 33 | } 34 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/KeyframesDrawableAnimationCallback.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes; 11 | 12 | import android.annotation.TargetApi; 13 | import android.os.Build; 14 | import android.os.Handler; 15 | import android.os.Looper; 16 | import android.os.SystemClock; 17 | import android.view.Choreographer; 18 | 19 | import com.facebook.keyframes.model.KFImage; 20 | 21 | import java.lang.ref.WeakReference; 22 | 23 | /** 24 | * A simple callback that when run, will call back indefinitely with progress updates until 25 | * cancelled. This will continuously feed back progress data from [0, 1] calculated by millis 26 | * per loop. 27 | */ 28 | public abstract class KeyframesDrawableAnimationCallback { 29 | 30 | /** 31 | * And interface for a class which wants to listen to progress updates. 32 | */ 33 | public interface FrameListener { 34 | void onProgressUpdate(float frameProgress); 35 | void onStop(); 36 | } 37 | 38 | private final WeakReference mListener; 39 | private final int mFrameCount; 40 | private final int mMillisPerLoop; 41 | 42 | private long mStartTimeMillis; 43 | private boolean mStopAtLoopEnd; 44 | private int mCurrentLoopNumber; 45 | 46 | private long mMinimumMillisBetweenProgressUpdates = -1; 47 | private long mPreviousProgressMillis = 0; 48 | 49 | /** 50 | * Creates a KeyframesDrawableAnimationCallback appropriate for the API level of the device. 51 | * @param listener The listener that will receive callbacks on updates to the value 52 | * @return A KeyframesDrawableAnimationCallback implementation 53 | */ 54 | public static KeyframesDrawableAnimationCallback create( 55 | FrameListener listener, 56 | KFImage face) { 57 | if (hasChoreographer()) { 58 | return new FrameCallbackFaceAnimationCallback( 59 | listener, 60 | face.getFrameRate(), 61 | face.getFrameCount()); 62 | } else { 63 | return new RunnableFaceAnimationCallback( 64 | listener, 65 | face.getFrameRate(), 66 | face.getFrameCount()); 67 | } 68 | } 69 | 70 | private static boolean hasChoreographer() { 71 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; 72 | } 73 | 74 | private KeyframesDrawableAnimationCallback(FrameListener listener, int frameRate, int frameCount) { 75 | mListener = new WeakReference<>(listener); 76 | mFrameCount = frameCount; 77 | mMillisPerLoop = Math.round(1000 * ((float) frameCount / frameRate)); 78 | } 79 | 80 | /** 81 | * Set the maximum frame rate for this animation. 82 | * Consider using this for low end devices. 83 | * @param maxFrameRate 84 | */ 85 | public void setMaxFrameRate(int maxFrameRate) { 86 | mMinimumMillisBetweenProgressUpdates = 1000 / maxFrameRate; 87 | } 88 | 89 | protected abstract void postCallback(); 90 | 91 | protected abstract void cancelCallback(); 92 | 93 | /** 94 | * Starts this animation callback and resets the start time. 95 | * 96 | * !IMPORTANT! This animator will run indefinitely, so it must be cancelled via #stop() 97 | * or #pause() when no longer in use! 98 | */ 99 | public void start() { 100 | mStopAtLoopEnd = false; 101 | mStartTimeMillis = 0; 102 | mCurrentLoopNumber = -1; 103 | cancelCallback(); 104 | postCallback(); 105 | } 106 | 107 | /** 108 | * Starts the animation and plays it once 109 | */ 110 | public void playOnce() { 111 | mStopAtLoopEnd = true; 112 | mStartTimeMillis = 0; 113 | mCurrentLoopNumber = 0; 114 | cancelCallback(); 115 | postCallback(); 116 | } 117 | 118 | /** 119 | * Stops the callbacks animation and resets the start time. 120 | */ 121 | public void stop() { 122 | cancelCallback(); 123 | mStartTimeMillis = 0; 124 | mCurrentLoopNumber = -1; 125 | mListener.get().onStop(); 126 | } 127 | 128 | /** 129 | * Pauses the callbacks animation and saves start time. 130 | */ 131 | public void pause() { 132 | cancelCallback(); 133 | mStartTimeMillis *= -1; 134 | } 135 | 136 | /** 137 | * Resumes this animation callback. 138 | * 139 | * !IMPORTANT! This animator will run indefinitely, so it must be cancelled via #stop() 140 | * or #pause() when no longer in use! 141 | */ 142 | public void resume() { 143 | mStopAtLoopEnd = false; 144 | cancelCallback(); 145 | postCallback(); 146 | } 147 | 148 | 149 | /** 150 | * Stops looping the animation, but finishes the current animation. 151 | */ 152 | public void stopAtLoopEnd() { 153 | mStopAtLoopEnd = true; 154 | } 155 | 156 | protected void advanceAnimation(final long frameTimeMillis) { 157 | if (mListener.get() == null) { 158 | cancelCallback(); 159 | mStartTimeMillis = 0; 160 | mPreviousProgressMillis = 0; 161 | mCurrentLoopNumber = -1; 162 | return; 163 | } 164 | if (mStartTimeMillis == 0) { 165 | mStartTimeMillis = frameTimeMillis; 166 | } else if (mStartTimeMillis < 0) { 167 | long pausedTimeMillis = frameTimeMillis - mPreviousProgressMillis; 168 | mStartTimeMillis = mStartTimeMillis * -1 + pausedTimeMillis; 169 | mPreviousProgressMillis += pausedTimeMillis; 170 | } 171 | 172 | int currentLoopNumber = (int) (frameTimeMillis - mStartTimeMillis) / mMillisPerLoop; 173 | final boolean loopHasEnded = currentLoopNumber > mCurrentLoopNumber; 174 | if (mStopAtLoopEnd && loopHasEnded) { 175 | mListener.get().onProgressUpdate(mFrameCount); 176 | stop(); 177 | return; 178 | } 179 | long currentProgressMillis = (frameTimeMillis - mStartTimeMillis) % mMillisPerLoop; 180 | 181 | boolean shouldUpdateProgress = true; 182 | if (frameTimeMillis - mPreviousProgressMillis < mMinimumMillisBetweenProgressUpdates) { 183 | shouldUpdateProgress = false; 184 | } else { 185 | mPreviousProgressMillis = frameTimeMillis; 186 | } 187 | 188 | if (shouldUpdateProgress) { 189 | mListener.get().onProgressUpdate((float) currentProgressMillis / mMillisPerLoop * mFrameCount); 190 | } 191 | mCurrentLoopNumber = (int) (frameTimeMillis - mStartTimeMillis) / mMillisPerLoop; 192 | postCallback(); 193 | } 194 | 195 | @TargetApi(16) 196 | private static class FrameCallbackFaceAnimationCallback extends KeyframesDrawableAnimationCallback 197 | implements Choreographer.FrameCallback { 198 | 199 | private final Choreographer mChoreographer; 200 | 201 | private FrameCallbackFaceAnimationCallback( 202 | FrameListener listener, 203 | int frameRate, 204 | int frameCount) { 205 | super(listener, frameRate, frameCount); 206 | mChoreographer = Choreographer.getInstance(); 207 | } 208 | 209 | @Override 210 | public void doFrame(long frameTimeNanos) { 211 | advanceAnimation(frameTimeNanos / 1000000); // nanoseconds per millisecond 212 | } 213 | 214 | @Override 215 | protected void postCallback() { 216 | mChoreographer.postFrameCallback(this); 217 | } 218 | 219 | @Override 220 | protected void cancelCallback() { 221 | mChoreographer.removeFrameCallback(this); 222 | } 223 | } 224 | 225 | private static class RunnableFaceAnimationCallback extends KeyframesDrawableAnimationCallback 226 | implements Runnable { 227 | private static final int ANIMATION_MIN_STEP_TIME_MS = 25; // 40 fps 228 | 229 | private final Handler mHandler; 230 | 231 | private RunnableFaceAnimationCallback( 232 | FrameListener listener, 233 | int frameRate, 234 | int frameCount) { 235 | super(listener, frameRate, frameCount); 236 | mHandler = new Handler(Looper.getMainLooper()); 237 | } 238 | 239 | @Override 240 | public void run() { 241 | advanceAnimation(SystemClock.uptimeMillis()); 242 | } 243 | 244 | @Override 245 | protected void postCallback() { 246 | mHandler.postDelayed(this, ANIMATION_MIN_STEP_TIME_MS); 247 | } 248 | 249 | @Override 250 | protected void cancelCallback() { 251 | mHandler.removeCallbacks(this); 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/KeyframesDrawableBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes; 11 | 12 | import android.graphics.Bitmap; 13 | 14 | import com.facebook.keyframes.model.KFImage; 15 | 16 | import java.util.Map; 17 | 18 | /** 19 | * A class for building an instance of the Keyframes drawable. This class includes setting configs 20 | * allowed on a KeyframesDrawable, as well as access to experimental features via 21 | * {@link ExperimentalFeatures}. 22 | */ 23 | public class KeyframesDrawableBuilder { 24 | 25 | private KFImage mImage; 26 | private int mMaxFrameRate = 60; 27 | private ExperimentalFeatures mExperimentalFeatures = new ExperimentalFeatures(); 28 | 29 | public KeyframesDrawable build() { 30 | if (mImage == null) { 31 | throw new IllegalArgumentException("No KFImage provided!"); 32 | } 33 | return new KeyframesDrawable(this); 34 | } 35 | 36 | public KeyframesDrawableBuilder withImage(KFImage image) { 37 | mImage = image; 38 | return this; 39 | } 40 | 41 | KFImage getImage() { 42 | return mImage; 43 | } 44 | 45 | public KeyframesDrawableBuilder withMaxFrameRate(int maxFrameRate) { 46 | mMaxFrameRate = maxFrameRate; 47 | return this; 48 | } 49 | 50 | int getMaxFrameRate() { 51 | return mMaxFrameRate; 52 | } 53 | 54 | // Experimental features below. APIs are volatile and subject to change. Use with care! 55 | 56 | public ExperimentalFeatures withExperimentalFeatures() { 57 | return mExperimentalFeatures; 58 | } 59 | 60 | ExperimentalFeatures getExperimentalFeatures() { 61 | return mExperimentalFeatures; 62 | } 63 | 64 | /** 65 | * Experimental features that are subject to drastic changes. Use with care! 66 | */ 67 | public class ExperimentalFeatures { 68 | private Map mBitmaps; 69 | private boolean mClipToAECanvas = false; 70 | 71 | public KeyframesDrawable build() { 72 | return KeyframesDrawableBuilder.this.build(); 73 | } 74 | 75 | public ExperimentalFeatures withBitmaps() { 76 | mBitmaps = mImage.getBitmaps(); 77 | return ExperimentalFeatures.this; 78 | } 79 | 80 | Map getBitmaps() { 81 | return mBitmaps; 82 | } 83 | 84 | public ExperimentalFeatures clipToAECanvas() { 85 | mClipToAECanvas = true; 86 | return ExperimentalFeatures.this; 87 | } 88 | 89 | boolean getClipToAECanvas() { 90 | return mClipToAECanvas; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/AbstractListDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import java.io.IOException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import android.util.JsonReader; 17 | 18 | /** 19 | * An generic abstract class to aid with deserializing a list of objects. 20 | * @param 21 | */ 22 | public abstract class AbstractListDeserializer { 23 | 24 | /** 25 | * Given a {@link JsonReader} object at the start of a list, read through the list and return 26 | * a List with the given objects of type {@link T}. 27 | * @param reader The current {@link JsonReader} at the start of a list of {@link T} 28 | * @return List of type {@link T} objects 29 | * @throws IOException 30 | */ 31 | public final List readList(JsonReader reader) throws IOException { 32 | List list = new ArrayList<>(); 33 | reader.beginArray(); 34 | while (reader.hasNext()) { 35 | list.add(this.readObjectImpl(reader)); 36 | } 37 | reader.endArray(); 38 | return list; 39 | } 40 | 41 | /** 42 | * Read one object of type {@link T} from the JsonReader and return it. 43 | * @param reader The current {@link JsonReader} 44 | * @return An object of type {@link T} 45 | * @throws IOException 46 | */ 47 | abstract T readObjectImpl(JsonReader reader) throws IOException; 48 | } 49 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/CommonDeserializerHelper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import java.io.IOException; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | import android.util.JsonReader; 18 | 19 | /** 20 | * Some useful common deserializers that are used by multiple Keyframes model objects. 21 | */ 22 | public class CommonDeserializerHelper { 23 | 24 | /** 25 | * {@link AbstractListDeserializer} implementation for a list of {@link String}s. 26 | */ 27 | public static final AbstractListDeserializer STRING_LIST_DESERIALIZER = 28 | new AbstractListDeserializer() { 29 | @Override 30 | String readObjectImpl(JsonReader reader) throws IOException { 31 | return reader.nextString(); 32 | } 33 | }; 34 | 35 | /** 36 | * {@link AbstractListDeserializer} implementation for a list of {@link Float}s. 37 | */ 38 | private static final AbstractListDeserializer FLOAT_LIST_DESERIALIZER = 39 | new AbstractListDeserializer() { 40 | @Override 41 | Float readObjectImpl(JsonReader reader) throws IOException { 42 | return (float) reader.nextDouble(); 43 | } 44 | }; 45 | 46 | /** 47 | * {@link AbstractListDeserializer} implementation for a List>. 48 | */ 49 | private static final AbstractListDeserializer> FLOAT_LIST_2D_DESERIALIZER = 50 | new AbstractListDeserializer>() { 51 | @Override 52 | List readObjectImpl(JsonReader reader) throws IOException { 53 | return FLOAT_LIST_DESERIALIZER.readList(reader); 54 | } 55 | }; 56 | 57 | /** 58 | * {@link AbstractListDeserializer} implementation for a List>>. 59 | */ 60 | private static final AbstractListDeserializer>> FLOAT_LIST_3D_DESERIALIZER = 61 | new AbstractListDeserializer>>() { 62 | @Override 63 | List> readObjectImpl(JsonReader reader) throws IOException { 64 | return FLOAT_LIST_2D_DESERIALIZER.readList(reader); 65 | } 66 | }; 67 | 68 | /** 69 | * Reads in a float array from {@link JsonReader} and returns a primitive float array 70 | * @param reader The current {@link JsonReader} 71 | * @return a float[], containing floats parsed from {@link JsonReader} 72 | * @throws IOException 73 | */ 74 | public static float[] readFloatArray(JsonReader reader) throws IOException { 75 | return convertListToPrimitiveArray(FLOAT_LIST_DESERIALIZER.readList(reader)); 76 | } 77 | 78 | /** 79 | * Reads in a 3D float array from {@link JsonReader}, and returns a 3D primitive float array 80 | * @param reader The currnet {@link JsonReader} 81 | * @return a float[][][], containing the floats parsed from {@link JsonReader} 82 | * @throws IOException 83 | */ 84 | public static float[][][] read3DFloatArray(JsonReader reader) throws IOException { 85 | return convert3DListToPrimitiveArray(FLOAT_LIST_3D_DESERIALIZER.readList(reader)); 86 | } 87 | 88 | /** 89 | * A helper method which converts a List to a primitive array float[]. 90 | * @param list The List to convert 91 | * @return a float[], representing the {@param list} passed in 92 | */ 93 | private static float[] convertListToPrimitiveArray(List list) { 94 | float[] result = new float[list.size()]; 95 | for (int i = 0, len = list.size(); i < len; i++) { 96 | result[i] = list.get(i); 97 | } 98 | return result; 99 | } 100 | 101 | /** 102 | * A helper method which converts List>> to a primitive array float[][][]. 103 | * @param list The List>> to convert 104 | * @return a float[][][], representing the {@param list} passed in 105 | */ 106 | private static float[][][] convert3DListToPrimitiveArray(List>> list) { 107 | float[][][] primaryArray = new float[list.size()][][]; 108 | for (int i = 0, primaryLen = list.size(); i < primaryLen; i++) { 109 | float[][] secondaryArray = new float[list.get(0).size()][]; 110 | for (int j = 0, secondaryLen = secondaryArray.length; j < secondaryLen; j++) { 111 | secondaryArray[j] = convertListToPrimitiveArray(list.get(i).get(j)); 112 | } 113 | primaryArray[i] = secondaryArray; 114 | } 115 | return primaryArray; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/KFAnimationDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import android.util.JsonReader; 13 | import com.facebook.keyframes.model.KFAnimation; 14 | 15 | import java.io.IOException; 16 | import java.util.Locale; 17 | 18 | /** 19 | * Deserializer for {@link KFAnimation}. 20 | * 21 | * Root deserializer starts at {@link KFImageDeserializer}. 22 | */ 23 | public class KFAnimationDeserializer { 24 | 25 | static final AbstractListDeserializer LIST_DESERIALIZER = 26 | new AbstractListDeserializer() { 27 | @Override 28 | KFAnimation readObjectImpl(JsonReader reader) throws IOException { 29 | return readObject(reader); 30 | } 31 | }; 32 | 33 | public static KFAnimation readObject(JsonReader reader) throws IOException { 34 | reader.beginObject(); 35 | KFAnimation.Builder builder = new KFAnimation.Builder(); 36 | while (reader.hasNext()) { 37 | String name = reader.nextName(); 38 | switch (name) { 39 | case KFAnimation.PROPERTY_TYPE_JSON_FIELD: 40 | builder.propertyType = KFAnimation.PropertyType.valueOf( 41 | reader.nextString().toUpperCase(Locale.US)); 42 | break; 43 | case KFAnimation.ANIMATION_FRAMES_JSON_FIELD: 44 | builder.animationFrames = 45 | KFAnimationFrameDeserializer.LIST_DESERIALIZER.readList(reader); 46 | break; 47 | case KFAnimation.TIMING_CURVES_JSON_FIELD: 48 | builder.timingCurves = CommonDeserializerHelper.read3DFloatArray(reader); 49 | break; 50 | case KFAnimation.ANCHOR_JSON_FIELD: 51 | builder.anchor = CommonDeserializerHelper.readFloatArray(reader); 52 | break; 53 | default: 54 | reader.skipValue(); 55 | } 56 | } 57 | reader.endObject(); 58 | return builder.build(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/KFAnimationFrameDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import java.io.IOException; 13 | 14 | import android.util.JsonReader; 15 | 16 | import com.facebook.keyframes.model.KFAnimationFrame; 17 | 18 | /** 19 | * Deserializer for {@link KFAnimationFrame}. 20 | * 21 | * Root deserializer starts at {@link KFImageDeserializer}. 22 | */ 23 | public class KFAnimationFrameDeserializer { 24 | 25 | static final AbstractListDeserializer LIST_DESERIALIZER = 26 | new AbstractListDeserializer() { 27 | @Override 28 | KFAnimationFrame readObjectImpl(JsonReader reader) throws IOException { 29 | return readObject(reader); 30 | } 31 | }; 32 | 33 | public static KFAnimationFrame readObject(JsonReader reader) throws IOException { 34 | reader.beginObject(); 35 | KFAnimationFrame.Builder builder = new KFAnimationFrame.Builder(); 36 | while (reader.hasNext()) { 37 | String name = reader.nextName(); 38 | switch (name) { 39 | case KFAnimationFrame.START_FRAME_JSON_FIELD: 40 | builder.startFrame = reader.nextInt(); 41 | break; 42 | case KFAnimationFrame.DATA_JSON_FIELD: 43 | builder.data = CommonDeserializerHelper.readFloatArray(reader); 44 | break; 45 | default: 46 | reader.skipValue(); 47 | } 48 | } 49 | reader.endObject(); 50 | return builder.build(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/KFAnimationGroupDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import java.io.IOException; 13 | 14 | import android.util.JsonReader; 15 | 16 | import com.facebook.keyframes.model.KFAnimationGroup; 17 | 18 | /** 19 | * Deserializer for {@link KFAnimationGroup}. 20 | * 21 | * Root deserializer starts at {@link KFImageDeserializer}. 22 | */ 23 | public class KFAnimationGroupDeserializer { 24 | 25 | static final AbstractListDeserializer LIST_DESERIALIZER = 26 | new AbstractListDeserializer() { 27 | @Override 28 | KFAnimationGroup readObjectImpl(JsonReader reader) throws IOException { 29 | return readObject(reader); 30 | } 31 | }; 32 | 33 | public static KFAnimationGroup readObject(JsonReader reader) throws IOException { 34 | reader.beginObject(); 35 | KFAnimationGroup.Builder builder = new KFAnimationGroup.Builder(); 36 | while (reader.hasNext()) { 37 | String name = reader.nextName(); 38 | switch (name) { 39 | case KFAnimationGroup.GROUP_ID_JSON_FIELD: 40 | builder.groupId = reader.nextInt(); 41 | break; 42 | case KFAnimationGroup.PARENT_GROUP_JSON_FIELD: 43 | builder.parentGroup = reader.nextInt(); 44 | break; 45 | case KFAnimationGroup.ANIMATIONS_JSON_FIELD: 46 | builder.animations = KFAnimationDeserializer.LIST_DESERIALIZER.readList(reader); 47 | break; 48 | default: 49 | reader.skipValue(); 50 | } 51 | } 52 | reader.endObject(); 53 | return builder.build(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/KFColorFrameDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import java.io.IOException; 13 | 14 | import android.graphics.Color; 15 | import android.util.JsonReader; 16 | 17 | import com.facebook.keyframes.model.KFColorFrame; 18 | 19 | /** 20 | * Deserializer for {@link KFColorFrame}. 21 | * 22 | * Root deserializer starts at {@link KFImageDeserializer}. 23 | */ 24 | public class KFColorFrameDeserializer { 25 | 26 | static final AbstractListDeserializer LIST_DESERIALIZER = 27 | new AbstractListDeserializer() { 28 | @Override 29 | KFColorFrame readObjectImpl(JsonReader reader) throws IOException { 30 | return readObject(reader); 31 | } 32 | }; 33 | 34 | public static KFColorFrame readObject(JsonReader reader) throws IOException { 35 | reader.beginObject(); 36 | KFColorFrame.Builder builder = new KFColorFrame.Builder(); 37 | while (reader.hasNext()) { 38 | String name = reader.nextName(); 39 | switch (name) { 40 | case KFColorFrame.START_FRAME_JSON_FIELD: 41 | builder.startFrame = reader.nextInt(); 42 | break; 43 | case KFColorFrame.COLOR_JSON_FIELD: 44 | builder.color = Color.parseColor(reader.nextString()); 45 | break; 46 | default: 47 | reader.skipValue(); 48 | } 49 | } 50 | reader.endObject(); 51 | return builder.build(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/KFFeatureDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import android.graphics.Color; 13 | import android.graphics.Paint; 14 | import android.util.JsonReader; 15 | 16 | import java.io.IOException; 17 | import java.util.Locale; 18 | 19 | import com.facebook.keyframes.model.KFFeature; 20 | 21 | /** 22 | * Deserializer for {@link KFFeature}. 23 | * 24 | * Root deserializer starts at {@link KFImageDeserializer}. 25 | */ 26 | public class KFFeatureDeserializer { 27 | 28 | static final AbstractListDeserializer LIST_DESERIALIZER = 29 | new AbstractListDeserializer() { 30 | @Override 31 | KFFeature readObjectImpl(JsonReader reader) throws IOException { 32 | return readObject(reader); 33 | } 34 | }; 35 | 36 | public static KFFeature readObject(JsonReader reader) throws IOException { 37 | reader.beginObject(); 38 | KFFeature.Builder builder = new KFFeature.Builder(); 39 | while (reader.hasNext()) { 40 | String name = reader.nextName(); 41 | switch (name) { 42 | case KFFeature.NAME_JSON_FIELD: 43 | builder.name = reader.nextString(); 44 | break; 45 | case KFFeature.FILL_COLOR_JSON_FIELD: 46 | builder.fillColor = Color.parseColor(reader.nextString()); 47 | break; 48 | case KFFeature.STROKE_COLOR_JSON_FIELD: 49 | builder.strokeColor = Color.parseColor(reader.nextString()); 50 | break; 51 | case KFFeature.STROKE_WIDTH_JSON_FIELD: 52 | builder.strokeWidth = (float) reader.nextDouble(); 53 | break; 54 | case KFFeature.FROM_FRAME_JSON_FIELD: 55 | builder.fromFrame = (float) reader.nextDouble(); 56 | break; 57 | case KFFeature.TO_FRAME_JSON_FIELD: 58 | builder.toFrame = (float) reader.nextDouble(); 59 | break; 60 | case KFFeature.KEY_FRAMES_JSON_FIELD: 61 | builder.keyFrames = KFFeatureFrameDeserializer.LIST_DESERIALIZER.readList(reader); 62 | break; 63 | case KFFeature.TIMING_CURVES_JSON_FIELD: 64 | builder.timingCurves = CommonDeserializerHelper.read3DFloatArray(reader); 65 | break; 66 | case KFFeature.ANIMATION_GROUP_JSON_FIELD: 67 | builder.animationGroup = reader.nextInt(); 68 | break; 69 | case KFFeature.FEATURE_ANIMATIONS_JSON_FIELD: 70 | builder.featureAnimations = 71 | KFAnimationDeserializer.LIST_DESERIALIZER.readList(reader); 72 | break; 73 | case KFFeature.EFFECT_JSON_FIELD: 74 | builder.effect = KFFeatureEffectDeserializer.readObject(reader); 75 | break; 76 | case KFFeature.STROKE_LINE_CAP_JSON_FIELD: 77 | builder.strokeLineCap = Paint.Cap.valueOf(reader.nextString().toUpperCase(Locale.US)); 78 | break; 79 | case KFFeature.BACKED_IMAGE_NAME_JSON_FIELD: 80 | builder.backedImageName = reader.nextString(); 81 | break; 82 | case KFFeature.FEATURE_MASK_JSON_FIELD: 83 | builder.featureMask = KFFeatureDeserializer.readObject(reader); 84 | break; 85 | default: 86 | reader.skipValue(); 87 | } 88 | } 89 | reader.endObject(); 90 | return builder.build(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/KFFeatureEffectDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import java.io.IOException; 13 | 14 | import android.util.JsonReader; 15 | 16 | import com.facebook.keyframes.model.KFFeatureEffect; 17 | 18 | /** 19 | * Deserializer for {@link KFFeatureEffect}. 20 | * 21 | * Root deserializer starts at {@link KFImageDeserializer}. 22 | */ 23 | public class KFFeatureEffectDeserializer { 24 | 25 | public static KFFeatureEffect readObject(JsonReader reader) throws IOException { 26 | reader.beginObject(); 27 | KFFeatureEffect.Builder builder = new KFFeatureEffect.Builder(); 28 | while (reader.hasNext()) { 29 | String name = reader.nextName(); 30 | switch (name) { 31 | case KFFeatureEffect.GRADIENT_JSON_FIELD: 32 | builder.gradient = KFGradientDeserializer.readObject(reader); 33 | break; 34 | default: 35 | reader.skipValue(); 36 | } 37 | } 38 | reader.endObject(); 39 | return builder.build(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/KFFeatureFrameDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import java.io.IOException; 13 | 14 | import android.util.JsonReader; 15 | 16 | import com.facebook.keyframes.model.KFFeatureFrame; 17 | 18 | /** 19 | * Deserializer for {@link KFFeatureFrame}. 20 | * 21 | * Root deserializer starts at {@link KFImageDeserializer}. 22 | */ 23 | public class KFFeatureFrameDeserializer { 24 | 25 | static final AbstractListDeserializer LIST_DESERIALIZER = 26 | new AbstractListDeserializer() { 27 | @Override 28 | KFFeatureFrame readObjectImpl(JsonReader reader) throws IOException { 29 | return readObject(reader); 30 | } 31 | }; 32 | 33 | public static KFFeatureFrame readObject(JsonReader reader) throws IOException { 34 | reader.beginObject(); 35 | KFFeatureFrame.Builder builder = new KFFeatureFrame.Builder(); 36 | while (reader.hasNext()) { 37 | String name = reader.nextName(); 38 | switch (name) { 39 | case KFFeatureFrame.START_FRAME_JSON_FIELD: 40 | builder.startFrame = reader.nextInt(); 41 | break; 42 | case KFFeatureFrame.DATA_JSON_FIELD: 43 | builder.data = CommonDeserializerHelper.STRING_LIST_DESERIALIZER.readList(reader); 44 | break; 45 | default: 46 | reader.skipValue(); 47 | } 48 | } 49 | reader.endObject(); 50 | return builder.build(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/KFGradientColorDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import java.io.IOException; 13 | 14 | import android.util.JsonReader; 15 | 16 | import com.facebook.keyframes.model.KFGradientColor; 17 | 18 | /** 19 | * Deserializer for {@link KFGradientColor}. 20 | * 21 | * Root deserializer starts at {@link KFImageDeserializer}. 22 | */ 23 | public class KFGradientColorDeserializer { 24 | 25 | public static KFGradientColor readObject(JsonReader reader) throws IOException { 26 | reader.beginObject(); 27 | KFGradientColor.Builder builder = new KFGradientColor.Builder(); 28 | while (reader.hasNext()) { 29 | String name = reader.nextName(); 30 | switch (name) { 31 | case KFGradientColor.KEY_VALUES_JSON_FIELD: 32 | builder.keyValues = KFColorFrameDeserializer.LIST_DESERIALIZER.readList(reader); 33 | break; 34 | case KFGradientColor.TIMING_CURVES_JSON_FIELD: 35 | builder.timingCurves = CommonDeserializerHelper.read3DFloatArray(reader); 36 | break; 37 | default: 38 | reader.skipValue(); 39 | } 40 | } 41 | reader.endObject(); 42 | return builder.build(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/KFGradientDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import java.io.IOException; 13 | 14 | import android.util.JsonReader; 15 | 16 | import com.facebook.keyframes.model.KFGradient; 17 | 18 | /** 19 | * Deserializer for {@link KFGradient}. 20 | * 21 | * Root deserializer starts at {@link KFImageDeserializer}. 22 | */ 23 | public class KFGradientDeserializer { 24 | 25 | static final AbstractListDeserializer LIST_DESERIALIZER = 26 | new AbstractListDeserializer() { 27 | @Override 28 | KFGradient readObjectImpl(JsonReader reader) throws IOException { 29 | return readObject(reader); 30 | } 31 | }; 32 | 33 | public static KFGradient readObject(JsonReader reader) throws IOException { 34 | reader.beginObject(); 35 | KFGradient.Builder builder = new KFGradient.Builder(); 36 | while (reader.hasNext()) { 37 | String name = reader.nextName(); 38 | switch (name) { 39 | case KFGradient.COLOR_START_JSON_FIELD: 40 | builder.colorStart = KFGradientColorDeserializer.readObject(reader); 41 | break; 42 | case KFGradient.COLOR_END_JSON_FIELD: 43 | builder.colorEnd = KFGradientColorDeserializer.readObject(reader); 44 | break; 45 | default: 46 | reader.skipValue(); 47 | } 48 | } 49 | reader.endObject(); 50 | return builder.build(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/deserializers/KFImageDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.deserializers; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.InputStreamReader; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import android.graphics.Bitmap; 19 | import android.graphics.BitmapFactory; 20 | import android.util.Base64; 21 | import android.util.JsonReader; 22 | 23 | import com.facebook.keyframes.model.KFImage; 24 | 25 | /** 26 | * Deserializer for {@link KFImage}. This is the root image object deserializer and contains the 27 | * {@link #deserialize(InputStream)} method, which takes an {@link InputStream} and attempts to 28 | * deserialize a full {@link KFImage} object from it. 29 | */ 30 | public class KFImageDeserializer { 31 | 32 | /** 33 | * Given an input stream, attempt to deserialize a {@link KFImage} and return it. 34 | * @param inputStream An input stream containing JSON for a {@link KFImage} 35 | * @return A deserialized {@link KFImage} object 36 | * @throws IOException 37 | */ 38 | public static KFImage deserialize(InputStream inputStream) throws IOException { 39 | JsonReader reader = new JsonReader(new InputStreamReader(inputStream)); 40 | return readObject(reader); 41 | } 42 | 43 | static KFImage readObject(JsonReader reader) throws IOException { 44 | reader.beginObject(); 45 | KFImage.Builder builder = new KFImage.Builder(); 46 | while (reader.hasNext()) { 47 | String name = reader.nextName(); 48 | switch (name) { 49 | case KFImage.FRAME_RATE_JSON_FIELD: 50 | builder.frameRate = reader.nextInt(); 51 | break; 52 | case KFImage.FRAME_COUNT_JSON_FIELD: 53 | builder.frameCount = reader.nextInt(); 54 | break; 55 | case KFImage.FEATURES_JSON_FIELD: 56 | builder.features = 57 | KFFeatureDeserializer.LIST_DESERIALIZER.readList(reader); 58 | break; 59 | case KFImage.ANIMATION_GROUPS_JSON_FIELD: 60 | builder.animationGroups = 61 | KFAnimationGroupDeserializer.LIST_DESERIALIZER.readList(reader); 62 | break; 63 | case KFImage.CANVAS_SIZE_JSON_FIELD: 64 | builder.canvasSize = CommonDeserializerHelper.readFloatArray(reader); 65 | break; 66 | case KFImage.KEY_JSON_FIELD: 67 | builder.key = reader.nextInt(); 68 | break; 69 | case KFImage.BITMAPS_JSON_FIELD: 70 | builder.bitmaps = readBitmaps(reader); 71 | break; 72 | default: 73 | reader.skipValue(); 74 | } 75 | } 76 | reader.endObject(); 77 | return builder.build(); 78 | } 79 | 80 | private static Map readBitmaps(JsonReader reader) throws IOException { 81 | reader.beginObject(); 82 | Map bitmaps = new HashMap<>(); 83 | while (reader.hasNext()) { 84 | String name = reader.nextName(); 85 | byte[] bytes = Base64.decode(reader.nextString(), Base64.DEFAULT); 86 | Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); 87 | bitmaps.put(name, bitmap); 88 | } 89 | reader.endObject(); 90 | return bitmaps; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/HasKeyFrame.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model; 11 | 12 | /** 13 | * An interface to identify an object which has a key frame associated with it. 14 | */ 15 | public interface HasKeyFrame { 16 | int getKeyFrame(); 17 | } 18 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/KFAnimation.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model; 11 | 12 | import java.util.Comparator; 13 | import java.util.List; 14 | 15 | import com.facebook.keyframes.model.keyframedmodels.KeyFramedAnchorPoint; 16 | import com.facebook.keyframes.model.keyframedmodels.KeyFramedMatrixAnimation; 17 | import com.facebook.keyframes.model.keyframedmodels.KeyFramedObject; 18 | import com.facebook.keyframes.model.keyframedmodels.KeyFramedOpacity; 19 | import com.facebook.keyframes.model.keyframedmodels.KeyFramedStrokeWidth; 20 | import com.facebook.keyframes.util.ArgCheckUtil; 21 | import com.facebook.keyframes.util.ListHelper; 22 | 23 | /** 24 | * The base class for describing an animated property. This can range from matrix based properties 25 | * such as scale, rotation, position, to others like stroke width. 26 | */ 27 | public class KFAnimation { 28 | 29 | public static final Comparator ANIMATION_PROPERTY_COMPARATOR = 30 | new Comparator() { 31 | @Override 32 | public int compare( 33 | KFAnimation lhs, KFAnimation rhs) { 34 | return lhs.getPropertyType().compareTo(rhs.getPropertyType()); 35 | } 36 | }; 37 | 38 | /** 39 | * Enum for the different supported animation types, listed in order of transform application. 40 | * Standard transform ordering is SCALE * ROTATION * TRANSLATION. 41 | */ 42 | public enum PropertyType { 43 | SCALE (true), 44 | ROTATION (true), 45 | POSITION (true), 46 | X_POSITION (true), 47 | Y_POSITION (true), 48 | ANCHOR_POINT (false), 49 | STROKE_WIDTH (false), 50 | OPACITY (false); 51 | 52 | /** 53 | * Whether this animation is matrix based or not. Currently, the only non-matrix based 54 | * animation is stroke width, which is specific to {@link KFFeature}. 55 | */ 56 | final boolean mIsMatrixBased; 57 | 58 | PropertyType(boolean isMatrixBased) { 59 | mIsMatrixBased = isMatrixBased; 60 | } 61 | 62 | /** 63 | * Returns whether this animation is matrix based or not. Currently, the only non-matrix based 64 | * animation is stroke width, which is specific to {@link KFFeature}. 65 | */ 66 | public boolean isMatrixBased() { 67 | return mIsMatrixBased; 68 | } 69 | } 70 | 71 | /** 72 | * The property animated by this KFAnimation instance. 73 | */ 74 | public static final String PROPERTY_TYPE_JSON_FIELD = "property"; 75 | private final PropertyType mPropertyType; 76 | 77 | /** 78 | * A list of {@link KFAnimationFrame}s, each describing timing and value data for a 79 | * specific key frame. 80 | */ 81 | public static final String ANIMATION_FRAMES_JSON_FIELD = "key_values"; 82 | private final List mAnimationFrames; 83 | 84 | /** 85 | * An array of timing curve data. This array describes transition values between each key frame 86 | * in mAnimationFrames. For each timing curve, there are two sets of points which are the control 87 | * points describing how to draw the cubic curve from (0,0) to (1,1). 88 | */ 89 | public static final String TIMING_CURVES_JSON_FIELD = "timing_curves"; 90 | private final float[][][] mTimingCurves; 91 | 92 | /** 93 | * An anchor point, which changes the origin of a matrix based property. 94 | * Deprecated in favor of the ANCHOR_POINT animation. 95 | */ 96 | @Deprecated 97 | public static final String ANCHOR_JSON_FIELD = "anchor"; 98 | @Deprecated 99 | private final float[] mAnchor; 100 | 101 | /** 102 | * A post-processed data structure containing cached information for this key frame animation. 103 | */ 104 | private final KeyFramedObject mKeyFramedAnimation; 105 | 106 | public static class Builder { 107 | public PropertyType propertyType; 108 | public List animationFrames; 109 | public float[][][] timingCurves; 110 | public float[] anchor; 111 | 112 | public KFAnimation build() { 113 | return new KFAnimation(propertyType, animationFrames, timingCurves, anchor); 114 | } 115 | } 116 | 117 | public KFAnimation( 118 | PropertyType propertyType, 119 | List animationFrames, 120 | float[][][] timingCurves, 121 | float[] anchor) { 122 | mPropertyType = ArgCheckUtil.checkArg( 123 | propertyType, 124 | propertyType != null, 125 | PROPERTY_TYPE_JSON_FIELD); 126 | mAnimationFrames = ArgCheckUtil.checkArg( 127 | ListHelper.immutableOrEmpty(animationFrames), 128 | animationFrames != null && animationFrames.size() > 0, 129 | ANIMATION_FRAMES_JSON_FIELD); 130 | mTimingCurves = ArgCheckUtil.checkArg( 131 | timingCurves, 132 | ArgCheckUtil.checkTimingCurveObjectValidity(timingCurves, mAnimationFrames.size()), 133 | TIMING_CURVES_JSON_FIELD); 134 | mAnchor = ArgCheckUtil.checkArg( 135 | anchor, 136 | anchor == null || anchor.length == 2, 137 | ANCHOR_JSON_FIELD); 138 | if (mPropertyType.isMatrixBased()) { 139 | mKeyFramedAnimation = KeyFramedMatrixAnimation.fromAnimation(this); 140 | } else if (mPropertyType == PropertyType.STROKE_WIDTH){ 141 | mKeyFramedAnimation = KeyFramedStrokeWidth.fromAnimation(this); 142 | } else if (mPropertyType == PropertyType.ANCHOR_POINT) { 143 | mKeyFramedAnimation = KeyFramedAnchorPoint.fromAnchorPoint(this); 144 | } else if (mPropertyType == PropertyType.OPACITY) { 145 | mKeyFramedAnimation = KeyFramedOpacity.fromAnimation(this); 146 | } else { 147 | throw new IllegalArgumentException( 148 | "Unknown property type for animation post processing: " + mPropertyType); 149 | } 150 | } 151 | 152 | public PropertyType getPropertyType() { 153 | return mPropertyType; 154 | } 155 | 156 | public List getAnimationFrames() { 157 | return mAnimationFrames; 158 | } 159 | 160 | public float[][][] getTimingCurves() { 161 | return mTimingCurves; 162 | } 163 | 164 | /** 165 | * Deprecated in favor of the ANCHOR_POINT animation. 166 | */ 167 | @Deprecated 168 | public float[] getAnchor() { 169 | return mAnchor; 170 | } 171 | 172 | public KeyFramedObject getAnimation() { 173 | return mKeyFramedAnimation; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/KFAnimationFrame.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model; 11 | 12 | import com.facebook.keyframes.util.ArgCheckUtil; 13 | 14 | /** 15 | * A simple class which wraps a float[] needed for one key frame. 16 | */ 17 | public class KFAnimationFrame implements HasKeyFrame { 18 | 19 | /** 20 | * The key frame # in the animation sequence. 21 | */ 22 | public static final String START_FRAME_JSON_FIELD = "start_frame"; 23 | private final int mStartFrame; 24 | 25 | /** 26 | * The values for this key frame. 27 | */ 28 | public static final String DATA_JSON_FIELD = "data"; 29 | private final float[] mData; 30 | 31 | public static class Builder { 32 | public int startFrame; 33 | public float[] data; 34 | 35 | public KFAnimationFrame build() { 36 | return new KFAnimationFrame(startFrame, data); 37 | } 38 | } 39 | 40 | private KFAnimationFrame(int startFrame, float[] data) { 41 | mStartFrame = startFrame; 42 | mData = ArgCheckUtil.checkArg( 43 | data, 44 | data.length > 0, 45 | DATA_JSON_FIELD); 46 | } 47 | 48 | @Override 49 | public int getKeyFrame() { 50 | return mStartFrame; 51 | } 52 | 53 | public float[] getData() { 54 | return mData; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/KFAnimationGroup.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model; 11 | 12 | import com.facebook.keyframes.model.keyframedmodels.KeyFramedAnchorPoint; 13 | import com.facebook.keyframes.util.AnimationHelper; 14 | import com.facebook.keyframes.util.ArgCheckUtil; 15 | import com.facebook.keyframes.util.ListHelper; 16 | 17 | import java.util.List; 18 | 19 | /** 20 | * A class representing a single animation layer of this animation. 21 | */ 22 | public class KFAnimationGroup { 23 | 24 | /** 25 | * An identifier for this animation layer, in case there are dependencies on it. 26 | */ 27 | public static final String GROUP_ID_JSON_FIELD = "group_id"; 28 | private final int mGroupId; 29 | 30 | /** 31 | * An identifier for a parent animation layer which this layer is nested in. 32 | */ 33 | public static final String PARENT_GROUP_JSON_FIELD = "parent_group"; 34 | private final int mParentGroup; 35 | 36 | /** 37 | * The different animations which are part of this layer. This should only include matrix based 38 | * animations since there are no features in this layer. 39 | */ 40 | public static final String ANIMATIONS_JSON_FIELD = "animations"; 41 | private final List mAnimations; 42 | 43 | private final KFAnimation mAnchorPoint; 44 | 45 | public static class Builder { 46 | public int groupId; 47 | public int parentGroup; 48 | public List animations; 49 | 50 | public KFAnimationGroup build() { 51 | return new KFAnimationGroup(groupId, parentGroup, animations); 52 | } 53 | } 54 | 55 | public KFAnimationGroup( 56 | int groupId, 57 | int parentGroup, 58 | List animations) { 59 | mGroupId = ArgCheckUtil.checkArg( 60 | groupId, 61 | groupId > 0, 62 | GROUP_ID_JSON_FIELD); 63 | mParentGroup = parentGroup; 64 | ListHelper.sort(animations, KFAnimation.ANIMATION_PROPERTY_COMPARATOR); 65 | mAnchorPoint = AnimationHelper.extractSpecialAnimationAnimationSet( 66 | animations, 67 | KFAnimation.PropertyType.ANCHOR_POINT); 68 | mAnimations = ArgCheckUtil.checkArg( 69 | ListHelper.immutableOrEmpty(animations), 70 | animations.size() > 0, 71 | ANIMATIONS_JSON_FIELD); 72 | } 73 | 74 | public int getGroupId() { 75 | return mGroupId; 76 | } 77 | 78 | public int getParentGroup() { 79 | return mParentGroup; 80 | } 81 | 82 | public List getAnimations() { 83 | return mAnimations; 84 | } 85 | 86 | public KeyFramedAnchorPoint getAnchorPoint() { 87 | if (mAnchorPoint == null) { 88 | return null; 89 | } 90 | return (KeyFramedAnchorPoint) mAnchorPoint.getAnimation(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/KFColorFrame.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model; 11 | 12 | /** 13 | * A simple class which wraps a color value needed for one key frame. 14 | */ 15 | public class KFColorFrame implements HasKeyFrame { 16 | 17 | /** 18 | * The key frame # in the animation sequence. 19 | */ 20 | public static final String START_FRAME_JSON_FIELD = "start_frame"; 21 | private final int mStartFrame; 22 | 23 | /** 24 | * The color value for this key frame. 25 | */ 26 | public static final String COLOR_JSON_FIELD = "data"; 27 | private final int mColor; 28 | 29 | public static class Builder { 30 | public int startFrame; 31 | public int color; 32 | 33 | public KFColorFrame build() { 34 | return new KFColorFrame(startFrame, color); 35 | } 36 | } 37 | 38 | private KFColorFrame(int startFrame, int color) { 39 | mStartFrame = startFrame; 40 | mColor = color; 41 | } 42 | 43 | @Override 44 | public int getKeyFrame() { 45 | return mStartFrame; 46 | } 47 | 48 | public int getColor() { 49 | return mColor; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/KFFeatureEffect.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model; 11 | 12 | import com.facebook.keyframes.util.ArgCheckUtil; 13 | 14 | /** 15 | * Describes an effect that can be applied to a feature layer. Currently, only a simple linear 16 | * gradient effect is supported. 17 | */ 18 | public class KFFeatureEffect { 19 | 20 | /** 21 | * The object containing gradient and gradient animation information. 22 | */ 23 | public static final String GRADIENT_JSON_FIELD = "gradient"; 24 | private final KFGradient mGradient; 25 | 26 | public static class Builder { 27 | public KFGradient gradient; 28 | 29 | public KFFeatureEffect build() { 30 | return new KFFeatureEffect(gradient); 31 | } 32 | } 33 | 34 | private KFFeatureEffect(KFGradient gradient) { 35 | mGradient = ArgCheckUtil.checkArg( 36 | gradient, 37 | gradient != null, 38 | GRADIENT_JSON_FIELD); 39 | } 40 | 41 | public KFGradient getGradient() { 42 | return mGradient; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/KFFeatureFrame.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import com.facebook.keyframes.KFPath; 16 | import com.facebook.keyframes.util.VectorCommand; 17 | import com.facebook.keyframes.util.ListHelper; 18 | 19 | /** 20 | * A simple class which wraps path command information needed for one key frame. 21 | */ 22 | public class KFFeatureFrame implements HasKeyFrame { 23 | 24 | /** 25 | * The key frame # in the animation sequence. 26 | */ 27 | public static final String START_FRAME_JSON_FIELD = "start_frame"; 28 | private final int mStartFrame; 29 | 30 | /** 31 | * The raw string commands for this feature in one key frame. 32 | */ 33 | public static final String DATA_JSON_FIELD = "data"; 34 | private final ShapeMoveListData mShapeData; 35 | 36 | public static class Builder { 37 | public int startFrame; 38 | public List data; 39 | 40 | public KFFeatureFrame build() { 41 | return new KFFeatureFrame(startFrame, data); 42 | } 43 | } 44 | 45 | public KFFeatureFrame(int startFrame, List data) { 46 | mStartFrame = startFrame; 47 | mShapeData = new ShapeMoveListData(data); 48 | } 49 | 50 | @Override 51 | public int getKeyFrame() { 52 | return mStartFrame; 53 | } 54 | 55 | public ShapeMoveListData getShapeData() { 56 | return mShapeData; 57 | } 58 | 59 | public static class ShapeMoveListData { 60 | 61 | private final List mVectorCommands; 62 | 63 | public ShapeMoveListData(List data) { 64 | List vectorCommandList = new ArrayList<>(); 65 | for (int i = 0, len = data.size(); i < len; i ++) { 66 | vectorCommandList.add(VectorCommand.createVectorCommand(data.get(i))); 67 | } 68 | mVectorCommands = ListHelper.immutableOrEmpty(vectorCommandList); 69 | } 70 | 71 | public void applyFeature(KFPath path) { 72 | for (int i = 0, len = mVectorCommands.size(); i < len; i++) { 73 | mVectorCommands.get(i).apply(path); 74 | } 75 | } 76 | 77 | public List getVectorCommands() { 78 | return mVectorCommands; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/KFGradient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model; 11 | 12 | import com.facebook.keyframes.model.keyframedmodels.KeyFramedGradient; 13 | import com.facebook.keyframes.util.ArgCheckUtil; 14 | 15 | import static com.facebook.keyframes.model.keyframedmodels.KeyFramedGradient.Position.*; 16 | 17 | /** 18 | * An object which wraps information for a gradient effect. Currently only supports simple linear 19 | * gradients. 20 | */ 21 | public class KFGradient { 22 | 23 | /** 24 | * To prevent allocating a lot of LinearGradient shaders during animation, and because it looks 25 | * looks like LinearGradient shader params can't be modified after instantiation, we cache the 26 | * shaders needed once at a precision of shaders per second listed here. 27 | */ 28 | 29 | /** 30 | * The start color of the gradient, the top color of the linear gradient. 31 | */ 32 | public static final String COLOR_START_JSON_FIELD = "color_start"; 33 | private final KeyFramedGradient mStartGradient; 34 | 35 | /** 36 | * The end color of the gradient, the bottom color of the linear gradient. 37 | */ 38 | public static final String COLOR_END_JSON_FIELD = "color_end"; 39 | private final KeyFramedGradient mEndGradient; 40 | 41 | public static class Builder { 42 | public KFGradientColor colorStart; 43 | public KFGradientColor colorEnd; 44 | 45 | public KFGradient build() { 46 | return new KFGradient(colorStart, colorEnd); 47 | } 48 | } 49 | 50 | public KFGradient(KFGradientColor colorStart, KFGradientColor colorEnd) { 51 | mStartGradient = KeyFramedGradient.fromGradient( 52 | ArgCheckUtil.checkArg( 53 | colorStart, 54 | colorStart != null, 55 | COLOR_START_JSON_FIELD), 56 | START); 57 | mEndGradient = KeyFramedGradient.fromGradient( 58 | ArgCheckUtil.checkArg( 59 | colorEnd, 60 | colorEnd != null, 61 | COLOR_END_JSON_FIELD), 62 | END); 63 | } 64 | 65 | public KeyFramedGradient getStartGradient() { 66 | return mStartGradient; 67 | } 68 | 69 | public KeyFramedGradient getEndGradient() { 70 | return mEndGradient; 71 | } 72 | } -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/KFGradientColor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model; 11 | 12 | import com.facebook.keyframes.util.ArgCheckUtil; 13 | import com.facebook.keyframes.util.ListHelper; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * The class which contains information about an animated gradient, including the key frame 19 | * information and timing curves. 20 | */ 21 | public class KFGradientColor { 22 | 23 | /** 24 | * A list of {@link KFColorFrame}s, each describing a color given a key frame. 25 | */ 26 | public static final String KEY_VALUES_JSON_FIELD = "key_values"; 27 | private final List mKeyValues; 28 | 29 | /** 30 | * A list of timing curves which describes how to interpolate between two 31 | * {@link KFColorFrame}s. 32 | */ 33 | public static final String TIMING_CURVES_JSON_FIELD = "timing_curves"; 34 | private final float[][][] mTimingCurves; 35 | 36 | public static class Builder { 37 | public List keyValues; 38 | public float[][][] timingCurves; 39 | 40 | public KFGradientColor build() { 41 | return new KFGradientColor(keyValues, timingCurves); 42 | } 43 | } 44 | 45 | public KFGradientColor(List keyValues, float[][][] timingCurves) { 46 | mKeyValues = ListHelper.immutableOrEmpty(keyValues); 47 | mTimingCurves = ArgCheckUtil.checkArg( 48 | timingCurves, 49 | ArgCheckUtil.checkTimingCurveObjectValidity(timingCurves, mKeyValues.size()), 50 | TIMING_CURVES_JSON_FIELD); 51 | } 52 | 53 | public List getKeyValues() { 54 | return mKeyValues; 55 | } 56 | 57 | public float[][][] getTimingCurves() { 58 | return mTimingCurves; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/KFImage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | package com.facebook.keyframes.model; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | import android.graphics.Bitmap; 15 | import android.graphics.Matrix; 16 | import android.util.SparseArray; 17 | 18 | import com.facebook.keyframes.util.AnimationGroupSort; 19 | import com.facebook.keyframes.util.ArgCheckUtil; 20 | import com.facebook.keyframes.util.ListHelper; 21 | 22 | /** 23 | * The top level model object for one entire animated image. Global information such as frame rate 24 | * it was exported as, frame count, and canvas size are included here for renderers. 25 | */ 26 | public class KFImage { 27 | 28 | /** 29 | * The frame rate that this animation was exported at. This is needed to play back the animation 30 | * at the correct speed. It does not limit the playback to discrete frames per second. 31 | */ 32 | public static final String FRAME_RATE_JSON_FIELD = "frame_rate"; 33 | private final int mFrameRate; 34 | 35 | /** 36 | * The total number of frames for this animation. 37 | */ 38 | public static final String FRAME_COUNT_JSON_FIELD = "animation_frame_count"; 39 | private final int mFrameCount; 40 | 41 | /** 42 | * A list of all the feature layers for this image. 43 | */ 44 | public static final String FEATURES_JSON_FIELD = "features"; 45 | private final List mFeatures; 46 | 47 | /** 48 | * A list of all the animation layers for this image. 49 | */ 50 | public static final String ANIMATION_GROUPS_JSON_FIELD = "animation_groups"; 51 | private final List mAnimationGroups; 52 | 53 | /** 54 | * The canvas size that this image was initially exported as. 55 | */ 56 | public static final String CANVAS_SIZE_JSON_FIELD = "canvas_size"; 57 | private final float[] mCanvasSize; 58 | 59 | /** 60 | * An optional identification key for this image. 61 | */ 62 | public static final String KEY_JSON_FIELD = "key"; 63 | private final int mKey; 64 | 65 | /** 66 | * An optional map of bitmaps for this image. 67 | */ 68 | public static final String BITMAPS_JSON_FIELD = "bitmaps"; 69 | private final Map mBitmaps; 70 | 71 | public static class Builder { 72 | public int frameRate; 73 | public int frameCount; 74 | public List features; 75 | public List animationGroups; 76 | public float[] canvasSize; 77 | public int key; 78 | public Map bitmaps; 79 | 80 | public KFImage build() { 81 | return new KFImage(frameRate, frameCount, features, animationGroups, canvasSize, key, bitmaps); 82 | } 83 | } 84 | 85 | private KFImage( 86 | int frameRate, 87 | int frameCount, 88 | List features, 89 | List animationGroups, 90 | float[] canvasSize, 91 | int key, 92 | Map bitmaps) { 93 | mFrameRate = ArgCheckUtil.checkArg( 94 | frameRate, 95 | frameRate > 0, 96 | FRAME_RATE_JSON_FIELD); 97 | mFrameCount = ArgCheckUtil.checkArg( 98 | frameCount, 99 | frameCount > 0, 100 | FRAME_COUNT_JSON_FIELD); 101 | mFeatures = ArgCheckUtil.checkArg( 102 | ListHelper.immutableOrEmpty(features), 103 | features.size() > 0, 104 | FEATURES_JSON_FIELD); 105 | animationGroups = AnimationGroupSort.sort(animationGroups); 106 | mAnimationGroups = 107 | ArgCheckUtil.checkArg( 108 | ListHelper.immutableOrEmpty(animationGroups), 109 | ArgCheckUtil.checkAnimationGroupIdUniqueness(animationGroups), 110 | ANIMATION_GROUPS_JSON_FIELD); 111 | mCanvasSize = ArgCheckUtil.checkArg( 112 | canvasSize, 113 | canvasSize.length == 2 && canvasSize[0] > 0 && canvasSize[1] > 0, 114 | CANVAS_SIZE_JSON_FIELD); 115 | mKey = key; 116 | mBitmaps = bitmaps; 117 | } 118 | 119 | public int getFrameRate() { 120 | return mFrameRate; 121 | } 122 | 123 | public int getFrameCount() { 124 | return mFrameCount; 125 | } 126 | 127 | public List getFeatures() { 128 | return mFeatures; 129 | } 130 | 131 | public List getAnimationGroups() { 132 | return mAnimationGroups; 133 | } 134 | 135 | /** 136 | * Given a map of group id and corresponding matrices, apply the current matrix state calculated 137 | * from progress in the animation to the matrix in the map. 138 | * @param matrixMap A prefilled map of animation group id -> matrix 139 | * @param frameProgress The progress in animation, given as a frame value 140 | */ 141 | public void setAnimationMatrices(SparseArray matrixMap, float frameProgress) { 142 | Matrix matrix; 143 | for (int groupIndex = 0, groupsLen = mAnimationGroups.size(); 144 | groupIndex < groupsLen; 145 | groupIndex++) { 146 | KFAnimationGroup group = mAnimationGroups.get(groupIndex); 147 | matrix = matrixMap.get(group.getGroupId()); 148 | matrix.reset(); 149 | if (group.getAnchorPoint() != null) { 150 | group.getAnchorPoint().apply(frameProgress, matrix); 151 | } 152 | for (int animationIndex = 0, animationsLen = group.getAnimations().size(); 153 | animationIndex < animationsLen; 154 | animationIndex++) { 155 | group.getAnimations().get(animationIndex).getAnimation().apply(frameProgress, matrix); 156 | } 157 | if (group.getParentGroup() > 0) { 158 | matrix.postConcat(matrixMap.get(group.getParentGroup())); 159 | } 160 | } 161 | } 162 | 163 | public float[] getCanvasSize() { 164 | return mCanvasSize; 165 | } 166 | 167 | public int getKey() { 168 | return mKey; 169 | } 170 | 171 | public Map getBitmaps() { 172 | return mBitmaps; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/keyframedmodels/KeyFrameAnimationHelper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model.keyframedmodels; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | import android.view.animation.Interpolator; 17 | 18 | import com.facebook.keyframes.util.KFPathInterpolator; 19 | 20 | /** 21 | * A helper class to build a list of interpolators corresponding to a list of timing curves, for use 22 | * with key framed animations to describe interpolation between key frames. 23 | */ 24 | public class KeyFrameAnimationHelper { 25 | 26 | /** 27 | * Given a list of timing curves consisting of an outTangent and an inTangent with x/y values, 28 | * returns an ImmutableList with a corresponding interpolator for each timing curve, in the same 29 | * order as supplied. 30 | */ 31 | public static List buildInterpolatorList(float[][][] timingCurves) { 32 | if (timingCurves == null) { 33 | return Collections.emptyList(); 34 | } 35 | List interpolatorList = new ArrayList<>(); 36 | for (int i = 0, len = timingCurves.length; i < len; i++) { 37 | float[][] influences = timingCurves[i]; 38 | interpolatorList.add( 39 | new KFPathInterpolator( 40 | influences[0][0], 41 | influences[0][1], 42 | influences[1][0], 43 | influences[1][1])); 44 | } 45 | return Collections.unmodifiableList(interpolatorList); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/keyframedmodels/KeyFramedAnchorPoint.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model.keyframedmodels; 11 | 12 | import android.graphics.Matrix; 13 | 14 | import com.facebook.keyframes.model.HasKeyFrame; 15 | import com.facebook.keyframes.model.KFAnimation; 16 | import com.facebook.keyframes.model.KFAnimationFrame; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * A special object that defines the anchor point for the other animations in a group or feature. 22 | */ 23 | public class KeyFramedAnchorPoint extends KeyFramedObject { 24 | 25 | public static KeyFramedAnchorPoint fromAnchorPoint(KFAnimation animation) { 26 | return new KeyFramedAnchorPoint(animation.getAnimationFrames(), animation.getTimingCurves()); 27 | } 28 | 29 | private KeyFramedAnchorPoint( 30 | List objects, 31 | float[][][] timingCurves) { 32 | super(objects, timingCurves); 33 | } 34 | 35 | @Override 36 | protected void applyImpl( 37 | KFAnimationFrame stateA, 38 | KFAnimationFrame stateB, 39 | float interpolationValue, 40 | Matrix matrix) { 41 | if (stateB == null) { 42 | matrix.postTranslate(-stateA.getData()[0], -stateA.getData()[1]); 43 | return; 44 | } 45 | matrix.postTranslate( 46 | -interpolateValue(stateA.getData()[0], stateB.getData()[0], interpolationValue), 47 | -interpolateValue(stateA.getData()[1], stateB.getData()[1], interpolationValue)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/keyframedmodels/KeyFramedGradient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model.keyframedmodels; 11 | 12 | import java.util.List; 13 | 14 | import android.graphics.Color; 15 | 16 | import com.facebook.keyframes.model.KFColorFrame; 17 | import com.facebook.keyframes.model.KFGradientColor; 18 | 19 | /** 20 | * A {link KeyFramedObject} which houses information for a gradient animation. This includes just 21 | * start and end colors. The modifiable class is a simple container object that wraps a start and 22 | * end color that can be modified and passed around. This is a post-process object used for 23 | * KFGradientColor. 24 | */ 25 | public class KeyFramedGradient 26 | extends KeyFramedObject { 27 | 28 | /** 29 | * An enum for whether this gradient color is for the start (top) or end (bottom) color. 30 | */ 31 | public enum Position { 32 | START, 33 | END 34 | } 35 | 36 | /** 37 | * A simple container that includes a start and end color which can be passed around and modified. 38 | * The setter methods can only be called by this class, since all other classes should only need 39 | * to grab values from the container. 40 | */ 41 | public static class GradientColorPair { 42 | private int mStartColor; 43 | private int mEndColor; 44 | 45 | public int getStartColor() { 46 | return mStartColor; 47 | } 48 | 49 | private void setStartColor(int startColor) { 50 | mStartColor = startColor; 51 | } 52 | 53 | public int getEndColor() { 54 | return mEndColor; 55 | } 56 | 57 | private void setEndColor(int endColor) { 58 | mEndColor = endColor; 59 | } 60 | } 61 | 62 | /** 63 | * Constructs a KeyFramedGradient from a {@link KFGradientColor}. 64 | * @param gradientColor The corresponding {@link KFGradientColor} 65 | * @param position The position of this color, either START or END 66 | */ 67 | public static KeyFramedGradient fromGradient( 68 | KFGradientColor gradientColor, 69 | Position position) { 70 | return new KeyFramedGradient( 71 | gradientColor.getKeyValues(), 72 | gradientColor.getTimingCurves(), 73 | position); 74 | } 75 | 76 | private final Position mPosition; 77 | 78 | private KeyFramedGradient( 79 | List objects, 80 | float[][][] timingCurves, 81 | Position position) { 82 | super(objects, timingCurves); 83 | mPosition = position; 84 | } 85 | 86 | /** 87 | * Applies the current state, given by the interpolationValue, to the {@link GradientColorPair}. 88 | * @param stateA Initial state 89 | * @param stateB End state 90 | * @param interpolationValue Progress [0..1] between stateA and stateB 91 | * @param modifiable The {@link GradientColorPair} to apply the values to 92 | */ 93 | @Override 94 | protected void applyImpl( 95 | KFColorFrame stateA, 96 | KFColorFrame stateB, 97 | float interpolationValue, 98 | GradientColorPair modifiable) { 99 | if (stateB == null) { 100 | if (mPosition == Position.START) { 101 | modifiable.setStartColor(stateA.getColor()); 102 | } else { 103 | modifiable.setEndColor(stateA.getColor()); 104 | } 105 | return; 106 | } 107 | if (mPosition == Position.START) { 108 | modifiable.setStartColor( 109 | getTransitionColor(interpolationValue, stateA.getColor(), stateB.getColor())); 110 | } else { 111 | modifiable.setEndColor( 112 | getTransitionColor(interpolationValue, stateA.getColor(), stateB.getColor())); 113 | } 114 | } 115 | 116 | /** 117 | * Given a start and end color, as well as a transition progress between the two, return a color 118 | * which is in between colorA and colorB, where 0 progress is colorA, 1 progress is colorB, and 119 | * all other progress values in between describe a linear transition. 120 | * @param progress Progress [0..1] between colorA and colorB. 121 | * @param colorA The color associated with progress 0 122 | * @param colorB The color associated with progress 1 123 | * @return The intermediate color that is {progress} between {colorA} and {colorB}. 124 | */ 125 | public static int getTransitionColor(float progress, int colorA, int colorB) { 126 | int startA = Color.alpha(colorA); 127 | int startR = Color.red(colorA); 128 | int startG = Color.green(colorA); 129 | int startB = Color.blue(colorA); 130 | 131 | int endA = Color.alpha(colorB); 132 | int endR = Color.red(colorB); 133 | int endG = Color.green(colorB); 134 | int endB = Color.blue(colorB); 135 | return ((startA + (int) (progress * (endA - startA))) << 24) | 136 | ((startR + (int) (progress * (endR - startR))) << 16) | 137 | ((startG + (int) (progress * (endG - startG))) << 8) | 138 | ((startB + (int) (progress * (endB - startB)))); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/keyframedmodels/KeyFramedMatrixAnimation.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model.keyframedmodels; 11 | 12 | import android.graphics.Matrix; 13 | import com.facebook.keyframes.model.KFAnimation; 14 | import com.facebook.keyframes.model.KFAnimationFrame; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * A {@link KeyFramedObject} which houses information for a matrix based animation. This includes 20 | * rotation, scale, and translation (position) information which can be applied to other animation 21 | * layers or feature layers. This is a post-process object used for {@link KFAnimation}. 22 | */ 23 | public class KeyFramedMatrixAnimation extends KeyFramedObject { 24 | 25 | /** 26 | * Constructs a KeyFramedMatrixAnimation from a {@link KFAnimation}. 27 | */ 28 | public static KeyFramedMatrixAnimation fromAnimation(KFAnimation animation) { 29 | if (!animation.getPropertyType().isMatrixBased()) { 30 | throw new IllegalArgumentException( 31 | "Cannot create a KeyFramedMatrixAnimation from a non matrix based KFAnimation."); 32 | } 33 | return new KeyFramedMatrixAnimation( 34 | animation.getAnimationFrames(), 35 | animation.getTimingCurves(), 36 | animation.getPropertyType(), 37 | animation.getAnchor()); 38 | } 39 | 40 | /** 41 | * The property type that is animated by this animation. 42 | */ 43 | private final KFAnimation.PropertyType mPropertyType; 44 | 45 | /** 46 | * The origin for this matrix animation. 47 | * Deprecated in favor of the ANCHOR_POINT animation. 48 | */ 49 | @Deprecated 50 | private final float[] mAnchor; 51 | 52 | private KeyFramedMatrixAnimation( 53 | List objects, 54 | float[][][] timingCurves, 55 | KFAnimation.PropertyType propertyType, 56 | float[] anchor) { 57 | super(objects, timingCurves); 58 | mPropertyType = propertyType; 59 | 60 | // Deprecated anchor behavior below 61 | mAnchor = anchor != null ? anchor : new float[2]; 62 | if (propertyType == KFAnimation.PropertyType.POSITION) { 63 | // Translations are special cased relative transforms. 64 | mAnchor[0] = objects.get(0).getData()[0]; 65 | mAnchor[1] = objects.get(0).getData()[1]; 66 | } 67 | } 68 | 69 | /** 70 | * Applies the current state, given by interpolationValue, to the Matrix object. Implementation 71 | * of the application method depends on the {@link KFAnimation.PropertyType} for this 72 | * animation. 73 | * @param stateA Initial state 74 | * @param stateB End state 75 | * @param interpolationValue Progress [0..1] between stateA and stateB 76 | * @param modifiable The matrix to apply the values to 77 | */ 78 | @Override 79 | protected void applyImpl( 80 | KFAnimationFrame stateA, 81 | KFAnimationFrame stateB, 82 | float interpolationValue, 83 | Matrix modifiable) { 84 | switch (mPropertyType) { 85 | case ROTATION: 86 | applyRotation(stateA, stateB, interpolationValue, modifiable); 87 | break; 88 | case SCALE: 89 | applyScale(stateA, stateB, interpolationValue, modifiable); 90 | break; 91 | case POSITION: 92 | applyPosition(stateA, stateB, interpolationValue, modifiable); 93 | break; 94 | case X_POSITION: 95 | applyXPosition(stateA, stateB, interpolationValue, modifiable); 96 | break; 97 | case Y_POSITION: 98 | applyYPosition(stateA, stateB, interpolationValue, modifiable); 99 | break; 100 | default: 101 | throw new UnsupportedOperationException( 102 | "Cannot apply matrix transformation to " + mPropertyType); 103 | } 104 | } 105 | 106 | /** 107 | * This method applies a rotational transform to the matrix, interpolated between two states. 108 | */ 109 | private void applyRotation( 110 | KFAnimationFrame stateA, 111 | KFAnimationFrame stateB, 112 | float interpolationValue, 113 | Matrix modifiable) { 114 | if (stateB == null) { 115 | modifiable.postRotate(stateA.getData()[0], 116 | mAnchor != null ? mAnchor[0] : 0, 117 | mAnchor != null ? mAnchor[1] : 0); 118 | return; 119 | } 120 | float rotationStart = stateA.getData()[0]; 121 | float rotationEnd = stateB.getData()[0]; 122 | modifiable.postRotate( 123 | interpolateValue(rotationStart, rotationEnd, interpolationValue), 124 | mAnchor != null ? mAnchor[0] : 0, 125 | mAnchor != null ? mAnchor[1] : 0); 126 | } 127 | 128 | /** 129 | * This method applies a scale transformation to the matrix, interpolated between two states. 130 | */ 131 | private void applyScale( 132 | KFAnimationFrame stateA, 133 | KFAnimationFrame stateB, 134 | float interpolationValue, 135 | Matrix modifiable) { 136 | if (stateB == null) { 137 | modifiable.postScale( 138 | stateA.getData()[0] / 100f, 139 | stateA.getData()[1] / 100f, 140 | mAnchor != null ? mAnchor[0] : 0, 141 | mAnchor != null ? mAnchor[1] : 0); 142 | return; 143 | } 144 | float scaleStartX = stateA.getData()[0]; 145 | float scaleEndX = stateB.getData()[0]; 146 | float scaleStartY = stateA.getData()[1]; 147 | float scaleEndY = stateB.getData()[1]; 148 | modifiable.postScale( 149 | interpolateValue(scaleStartX, scaleEndX, interpolationValue) / 100f, 150 | interpolateValue(scaleStartY, scaleEndY, interpolationValue) / 100f, 151 | mAnchor != null ? mAnchor[0] : 0, 152 | mAnchor != null ? mAnchor[1] : 0); 153 | } 154 | 155 | /** 156 | * This method applies an X translation transformation to the matrix, interpolated between two 157 | * states. 158 | */ 159 | private void applyXPosition( 160 | KFAnimationFrame stateA, 161 | KFAnimationFrame stateB, 162 | float interpolationValue, 163 | Matrix modifiable) { 164 | if (stateB == null) { 165 | modifiable.postTranslate(stateA.getData()[0], 0); 166 | return; 167 | } 168 | float translationStartX = stateA.getData()[0]; 169 | float translationEndX = stateB.getData()[0]; 170 | modifiable.postTranslate( 171 | interpolateValue(translationStartX, translationEndX, interpolationValue), 172 | 0); 173 | } 174 | 175 | /** 176 | * This method applies a Y translation transformation to the matrix, interpolated between two 177 | * states. 178 | */ 179 | private void applyYPosition( 180 | KFAnimationFrame stateA, 181 | KFAnimationFrame stateB, 182 | float interpolationValue, 183 | Matrix modifiable) { 184 | if (stateB == null) { 185 | modifiable.postTranslate(0, stateA.getData()[0]); 186 | return; 187 | } 188 | float translationStartY = stateA.getData()[0]; 189 | float translationEndY = stateB.getData()[0]; 190 | modifiable.postTranslate( 191 | 0, 192 | interpolateValue(translationStartY, translationEndY, interpolationValue)); 193 | } 194 | 195 | /** 196 | * This method applies a translation transformation to the matrix. Anchor points for a 197 | * translation animation are special cased to be relative to the position of the first frame 198 | * of this animation. This means that if the translation for the first frame is 80x, 80y, and the 199 | * translation for the second key frame is 90x, 70y, the resulting translation is 10x, -10y. 200 | * 201 | * Deprecated in favor of X_POSITION and Y_POSITION transforms. 202 | */ 203 | @Deprecated 204 | private void applyPosition( 205 | KFAnimationFrame stateA, 206 | KFAnimationFrame stateB, 207 | float interpolationValue, 208 | Matrix modifiable) { 209 | if (stateB == null) { 210 | return; 211 | } 212 | float translationStartX = stateA.getData()[0]; 213 | float translationEndX = stateB.getData()[0]; 214 | float translationStartY = stateA.getData()[1]; 215 | float translationEndY = stateB.getData()[1]; 216 | modifiable.postTranslate( 217 | interpolateValue(translationStartX, translationEndX, interpolationValue) - mAnchor[0], 218 | interpolateValue(translationStartY, translationEndY, interpolationValue) - mAnchor[1]); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/keyframedmodels/KeyFramedObject.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model.keyframedmodels; 11 | 12 | import android.util.SparseArray; 13 | import android.view.animation.Interpolator; 14 | import com.facebook.keyframes.model.HasKeyFrame; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * A generic object which holds key framed data and how to process information given a frame 20 | * progress. This object contains a generic method to apply properties of this object to a 21 | * modifyable object M (e.g. Path / Matrix / Generic Container) given a frame progress. 22 | * @param The type of animated object held by this KeyFramedObject 23 | * @param A modifiable object passed into the apply function to 24 | */ 25 | public abstract class KeyFramedObject { 26 | 27 | private final SparseArray mObjects; 28 | private final List mInterpolators; 29 | private final int mFirstDescribedFrame; 30 | private final int mLastDescribedFrame; 31 | 32 | public KeyFramedObject(List objects, float[][][] timingCurves) { 33 | int listSize = objects.size(); 34 | mObjects = new SparseArray<>(listSize); 35 | T object; 36 | for (int i = 0; i < listSize; i++) { 37 | object = objects.get(i); 38 | mObjects.put(object.getKeyFrame(), object); 39 | } 40 | 41 | mFirstDescribedFrame = mObjects.keyAt(0); 42 | mLastDescribedFrame = mObjects.keyAt(listSize - 1); 43 | mInterpolators = KeyFrameAnimationHelper.buildInterpolatorList(timingCurves); 44 | } 45 | 46 | /** 47 | * Constructor for creating empty/invalid KeyFramedObjects. 48 | */ 49 | protected KeyFramedObject() { 50 | mObjects = null; 51 | mInterpolators = null; 52 | mFirstDescribedFrame = 0; 53 | mLastDescribedFrame = 0; 54 | } 55 | 56 | /** 57 | * Applies the corresponding values determined by frameProgress to the modifiable object. This 58 | * method does some simple calculations to determine which key frames to use, as well as the 59 | * interpolation value between them based on the timing curve information, then delegates to 60 | * {@link #applyImpl(HasKeyFrame, HasKeyFrame, float, Object)} for the actual application to 61 | * the modifiable object. 62 | * @param frameProgress The progress, described in frames, of the animation. 63 | * @param modifiable The object to insert values into. 64 | */ 65 | public void apply(float frameProgress, M modifiable) { 66 | if (mInterpolators.isEmpty() || 67 | frameProgress <= mFirstDescribedFrame) { 68 | applyImpl(mObjects.get(mFirstDescribedFrame), null, 0, modifiable); 69 | return; 70 | } 71 | if (frameProgress >= mLastDescribedFrame) { 72 | applyImpl(mObjects.get(mLastDescribedFrame), null, 0, modifiable); 73 | return; 74 | } 75 | T thisFrame = null; 76 | T nextFrame = null; 77 | int interpolatorIndex; 78 | int len = mInterpolators.size(); 79 | for (interpolatorIndex = 0; interpolatorIndex < len; interpolatorIndex++) { 80 | if (mObjects.keyAt(interpolatorIndex) == frameProgress || 81 | (mObjects.keyAt(interpolatorIndex) < frameProgress && 82 | mObjects.keyAt(interpolatorIndex + 1) > frameProgress)) { 83 | thisFrame = mObjects.valueAt(interpolatorIndex); 84 | nextFrame = mObjects.valueAt(interpolatorIndex + 1); 85 | break; 86 | } 87 | } 88 | float progress = (frameProgress - thisFrame.getKeyFrame()) / 89 | (nextFrame.getKeyFrame() - thisFrame.getKeyFrame()); 90 | applyImpl( 91 | thisFrame, 92 | nextFrame, 93 | mInterpolators.get(interpolatorIndex).getInterpolation(progress), 94 | modifiable); 95 | } 96 | 97 | /** 98 | * Apply the given state to a modifiable. 99 | * @param stateA Initial state 100 | * @param stateB End state 101 | * @param interpolationValue Progress [0..1] between stateA and stateB 102 | * @param modifiable The modifiable object to apply the values to 103 | */ 104 | protected abstract void applyImpl( 105 | T stateA, 106 | T stateB, 107 | float interpolationValue, 108 | M modifiable); 109 | 110 | /** 111 | * Given two values and the progress from valueA to valueB, returns the transitional value in 112 | * between. 113 | */ 114 | protected static float interpolateValue(float valueA, float valueB, float progress) { 115 | return valueA + (valueB - valueA) * progress; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/keyframedmodels/KeyFramedOpacity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model.keyframedmodels; 11 | 12 | import java.util.List; 13 | 14 | import com.facebook.keyframes.model.KFAnimation; 15 | import com.facebook.keyframes.model.KFAnimation.PropertyType; 16 | import com.facebook.keyframes.model.KFAnimationFrame; 17 | 18 | 19 | /** 20 | * This is a special cased KFAnimation, since it is the only animation which is not applied 21 | * via a matrix. The information for this Modifiable is packed into a single length array. 22 | * A {@link KeyFramedObject} which houses information about a stroke width animation. This includes 23 | * float values for stroke width at any given key frame. This is a post-process object used for 24 | * KFAnimation. 25 | */ 26 | public class KeyFramedOpacity 27 | extends KeyFramedObject { 28 | 29 | /** 30 | * A container object so that this class can set values on an object which on a common reference. 31 | */ 32 | public static class Opacity { 33 | private float mOpacity = 100; 34 | 35 | public float getOpacity() { 36 | return mOpacity; 37 | } 38 | 39 | public void setOpacity(float opacity) { 40 | mOpacity = opacity; 41 | } 42 | } 43 | 44 | /** 45 | * Constructs a KeyFramedOpacity from a {@link KFAnimation}. 46 | */ 47 | public static KeyFramedOpacity fromAnimation(KFAnimation animation) { 48 | if (animation.getPropertyType() != KFAnimation.PropertyType.OPACITY) { 49 | throw new IllegalArgumentException( 50 | "Cannot create a KeyFramedOpacity object from a non OPACITY animation."); 51 | } 52 | return new KeyFramedOpacity(animation.getAnimationFrames(), animation.getTimingCurves()); 53 | } 54 | 55 | public KeyFramedOpacity( 56 | List objects, 57 | float[][][] timingCurves) { 58 | super(objects, timingCurves); 59 | } 60 | 61 | private KeyFramedOpacity() { 62 | super(); 63 | } 64 | 65 | /** 66 | * Applies the current state, given by interpolationValue, to the Opacity object. 67 | * @param stateA Initial state 68 | * @param stateB End state 69 | * @param interpolationValue Progress [0..1] between stateA and stateB 70 | * @param modifiable The Opacity to apply the values to 71 | */ 72 | @Override 73 | protected void applyImpl( 74 | KFAnimationFrame stateA, 75 | KFAnimationFrame stateB, 76 | float interpolationValue, 77 | Opacity modifiable) { 78 | if (stateB == null) { 79 | modifiable.setOpacity(stateA.getData()[0]); 80 | return; 81 | } 82 | modifiable.setOpacity( 83 | interpolateValue(stateA.getData()[0], stateB.getData()[0], interpolationValue)); 84 | } 85 | } -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/keyframedmodels/KeyFramedPath.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model.keyframedmodels; 11 | 12 | import java.util.List; 13 | 14 | import com.facebook.keyframes.KFPath; 15 | import com.facebook.keyframes.model.KFFeature; 16 | import com.facebook.keyframes.model.KFFeatureFrame; 17 | 18 | /** 19 | * A {@link KeyFramedObject} which houses information for a key framed feature object. This 20 | * includes the commands to draw a shape at each given key frame. This is a post-process object 21 | * used for KFFeature. 22 | */ 23 | public class KeyFramedPath extends KeyFramedObject { 24 | 25 | /** 26 | * Constructs a KeyFramedPath from a {@link KFFeature}. 27 | */ 28 | public static KeyFramedPath fromFeature(KFFeature feature) { 29 | return new KeyFramedPath(feature.getKeyFrames(), feature.getTimingCurves()); 30 | } 31 | 32 | private KeyFramedPath(List featureFrames, float[][][] timingCurves) { 33 | super(featureFrames, timingCurves); 34 | } 35 | 36 | /** 37 | * Applies the current state, given by interpolationValue, to the supplied Path object. 38 | * @param stateA Initial state 39 | * @param stateB End state 40 | * @param interpolationValue Progress [0..1] between stateA and stateB 41 | * @param modifiable The modifiable object to apply the values to 42 | */ 43 | @Override 44 | protected void applyImpl( 45 | KFFeatureFrame stateA, 46 | KFFeatureFrame stateB, 47 | float interpolationValue, 48 | KFPath modifiable) { 49 | if (stateB == null || interpolationValue == 0) { 50 | stateA.getShapeData().applyFeature(modifiable); 51 | return; 52 | } 53 | KFFeatureFrame.ShapeMoveListData thisMoveList = stateA.getShapeData(); 54 | KFFeatureFrame.ShapeMoveListData nextMoveList = stateB.getShapeData(); 55 | for (int i = 0, len = thisMoveList.getVectorCommands().size(); i < len; i++) { 56 | thisMoveList.getVectorCommands().get(i).interpolate( 57 | nextMoveList.getVectorCommands().get(i), 58 | interpolationValue, 59 | modifiable); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/model/keyframedmodels/KeyFramedStrokeWidth.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model.keyframedmodels; 11 | 12 | import java.util.List; 13 | 14 | import com.facebook.keyframes.model.KFAnimation; 15 | import com.facebook.keyframes.model.KFAnimationFrame; 16 | 17 | 18 | /** 19 | * This is a special cased KFAnimation, since it is the only animation which is not applied 20 | * via a matrix. The information for this Modifiable is packed into a single length array. 21 | * A {@link KeyFramedObject} which houses information about a stroke width animation. This includes 22 | * float values for stroke width at any given key frame. This is a post-process object used for 23 | * KFAnimation. 24 | */ 25 | public class KeyFramedStrokeWidth 26 | extends KeyFramedObject { 27 | 28 | /** 29 | * A container object so that this class can set values on an object which on a common reference. 30 | */ 31 | public static class StrokeWidth { 32 | private float mStrokeWidth; 33 | 34 | public float getStrokeWidth() { 35 | return Math.abs(mStrokeWidth); 36 | } 37 | 38 | public void setStrokeWidth(float strokeWidth) { 39 | mStrokeWidth = strokeWidth; 40 | } 41 | 42 | public void adjustScale(float scale) { 43 | mStrokeWidth *= scale; 44 | } 45 | } 46 | 47 | /** 48 | * Constructs a KeyFramedStrokeWidth from a {@link KFAnimation}. 49 | */ 50 | public static KeyFramedStrokeWidth fromAnimation(KFAnimation animation) { 51 | if (animation.getPropertyType() != KFAnimation.PropertyType.STROKE_WIDTH) { 52 | throw new IllegalArgumentException( 53 | "Cannot create a KeyFramedStrokeWidth object from a non STROKE_WIDTH animation."); 54 | } 55 | return new KeyFramedStrokeWidth(animation.getAnimationFrames(), animation.getTimingCurves()); 56 | } 57 | 58 | public KeyFramedStrokeWidth( 59 | List objects, 60 | float[][][] timingCurves) { 61 | super(objects, timingCurves); 62 | } 63 | 64 | private KeyFramedStrokeWidth() { 65 | super(); 66 | } 67 | 68 | /** 69 | * Applies the current state, given by interpolationValue, to the StrokeWidth object. 70 | * @param stateA Initial state 71 | * @param stateB End state 72 | * @param interpolationValue Progress [0..1] between stateA and stateB 73 | * @param modifiable The StrokeWidth to apply the values to 74 | */ 75 | @Override 76 | protected void applyImpl( 77 | KFAnimationFrame stateA, 78 | KFAnimationFrame stateB, 79 | float interpolationValue, 80 | StrokeWidth modifiable) { 81 | if (stateB == null) { 82 | modifiable.setStrokeWidth(stateA.getData()[0]); 83 | return; 84 | } 85 | modifiable.setStrokeWidth( 86 | interpolateValue(stateA.getData()[0], stateB.getData()[0], interpolationValue)); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/util/AnimationGroupSort.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.util; 11 | 12 | import com.facebook.keyframes.model.KFAnimationGroup; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.Iterator; 17 | import java.util.List; 18 | import java.util.Stack; 19 | 20 | public class AnimationGroupSort { 21 | 22 | /** 23 | * Sorts a list of {@link KFAnimationGroup} in topological order. The list passed in should be 24 | * mutable, and will be destroyed in the process. 25 | */ 26 | public static List sort(List animationGroupList) { 27 | if (animationGroupList == null || animationGroupList.isEmpty()) { 28 | return Collections.EMPTY_LIST; 29 | } 30 | List result = new ArrayList<>(); 31 | Stack rootNodes = new Stack<>(); 32 | 33 | KFAnimationGroup currGroup; 34 | for (Iterator iterator = animationGroupList.iterator(); 35 | iterator.hasNext();) { 36 | currGroup = iterator.next(); 37 | if (currGroup.getParentGroup() == 0) { 38 | rootNodes.push(currGroup); 39 | iterator.remove(); 40 | } 41 | } 42 | 43 | while (!rootNodes.isEmpty()) { 44 | currGroup = rootNodes.pop(); 45 | result.add(currGroup); 46 | for (Iterator iterator = animationGroupList.iterator(); 47 | iterator.hasNext();) { 48 | KFAnimationGroup childGroup = iterator.next(); 49 | if (childGroup.getParentGroup() == currGroup.getGroupId()) { 50 | rootNodes.push(childGroup); 51 | } 52 | } 53 | } 54 | return result; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/util/AnimationHelper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.util; 11 | 12 | import com.facebook.keyframes.model.KFAnimation; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * A helper class for parsing and extracting information from various KFAnimations. 18 | */ 19 | public class AnimationHelper { 20 | 21 | /** 22 | * Returns a special PropertyType KeyframeAnimation, if available. This method modifies the list 23 | * passed in by found entry from the list. 24 | * @param animations The complete list of feature animations to extract the animation from 25 | * @return a valid animation of the passed in type, if found, or null otherwise 26 | */ 27 | public static KFAnimation extractSpecialAnimationAnimationSet( 28 | List animations, 29 | KFAnimation.PropertyType specialAnimationType) { 30 | if (animations == null) { 31 | return null; 32 | } 33 | int specialAnimationIndex = -1; 34 | for (int i = 0, len = animations.size(); i < len; i++) { 35 | if (animations.get(i).getPropertyType() == specialAnimationType) { 36 | // Only case is a stroke width animation, special to feature animation set. Remove from the 37 | // set of matrix based animations and remember the index. 38 | specialAnimationIndex = i; 39 | break; 40 | } 41 | } 42 | if (specialAnimationIndex == -1) { 43 | return null; 44 | } 45 | return animations.remove(specialAnimationIndex); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/util/ArgCheckUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.util; 11 | 12 | import com.facebook.keyframes.model.KFAnimationGroup; 13 | 14 | import java.util.HashSet; 15 | import java.util.List; 16 | import java.util.Locale; 17 | import java.util.Set; 18 | 19 | public class ArgCheckUtil { 20 | 21 | /** 22 | * Checks the validity of an argument, given a condition. If the condition passes, the argument 23 | * is returned. If not, an IllegalArgumentException is thrown. 24 | * @param arg The argument 25 | * @param condition The evaluated condition 26 | * @param argName The name of the argument field for use in the exception, if needed. 27 | * @return The argument, if condition is valid. 28 | * @throws IllegalArgumentException if the condition is invalid 29 | */ 30 | public static T checkArg(T arg, boolean condition, String argName) { 31 | if (condition) { 32 | return arg; 33 | } 34 | throw new IllegalArgumentException( 35 | String.format(Locale.US, 36 | "Illegal argument for %s.", 37 | argName)); 38 | } 39 | 40 | /** 41 | * Checks that the format of a timing curve 3D float array is valid. The number of timing curves 42 | * for an animation should be equal to the number of keyframes - 1. 43 | * @param timingCurves the 3D float array to check 44 | * @param keyFrameQuantity the number of key frames this animation has 45 | * @return true if the format is valid, false otherwise 46 | */ 47 | public static boolean checkTimingCurveObjectValidity( 48 | float[][][] timingCurves, 49 | int keyFrameQuantity) { 50 | if (keyFrameQuantity <= 1 && (timingCurves == null || timingCurves.length == 0)) { 51 | return true; 52 | } 53 | if (keyFrameQuantity - 1 != timingCurves.length) { 54 | return false; 55 | } 56 | for (int i = 0; i < timingCurves.length; i++) { 57 | if (timingCurves[i].length != 2) { 58 | return false; 59 | } 60 | for (int j = 0; j < timingCurves[i].length; j++) { 61 | if (timingCurves[i][j].length != 2) { 62 | return false; 63 | } 64 | } 65 | } 66 | return true; 67 | } 68 | 69 | /** 70 | * Checks that the list of {@link KFAnimationGroup} all contain unique IDs. 71 | * @param groups the list of {@link KFAnimationGroup} 72 | * @return true if all IDs are unique, false otherwise 73 | */ 74 | public static boolean checkAnimationGroupIdUniqueness(List groups) { 75 | if (groups == null || groups.size() == 0) { 76 | return true; 77 | } 78 | Set keys = new HashSet<>(groups.size()); 79 | for (int i = 0, len = groups.size(); i < len; i++) { 80 | Integer groupId = groups.get(i).getGroupId(); 81 | if (keys.contains(groupId)) { 82 | return false; 83 | } 84 | keys.add(groupId); 85 | } 86 | return true; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/util/KFPathInterpolator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.facebook.keyframes.util; 18 | 19 | import android.graphics.Path; 20 | import android.graphics.PathMeasure; 21 | import android.view.animation.Interpolator; 22 | 23 | /** 24 | * This class is directly lifted from the compat library's 25 | * {@link android.support.v4.view.animation.PathInterpolatorCompat}. The reason we need this class 26 | * is to be able to control the precision ourselves for a smaller memory footprint. The normal 27 | * PathInterpolator allocates ~1400 floats every time and holds on to this in memory for the entire 28 | * lifetime of the object, while this one allocates only 10% of that. We dont need high precision 29 | * for what we are using this for. 30 | */ 31 | public class KFPathInterpolator implements Interpolator { 32 | /** 33 | * Governs the accuracy of the approximation of the {@link Path}. 34 | */ 35 | private static final float PRECISION = 0.03f; 36 | private final float[] mX; 37 | private final float[] mY; 38 | 39 | public KFPathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) { 40 | Path path = new Path(); 41 | path.moveTo(0, 0); 42 | path.cubicTo(controlX1, controlY1, controlX2, controlY2, 1f, 1f); 43 | final PathMeasure pathMeasure = new PathMeasure(path, false /* forceClosed */); 44 | final float pathLength = pathMeasure.getLength(); 45 | final int numPoints = (int) (pathLength / PRECISION) + 1; 46 | mX = new float[numPoints]; 47 | mY = new float[numPoints]; 48 | final float[] position = new float[2]; 49 | for (int i = 0; i < numPoints; ++i) { 50 | final float distance = (i * pathLength) / (numPoints - 1); 51 | pathMeasure.getPosTan(distance, position, null /* tangent */); 52 | mX[i] = position[0]; 53 | mY[i] = position[1]; 54 | } 55 | } 56 | 57 | @Override 58 | public float getInterpolation(float t) { 59 | if (t <= 0.0f) { 60 | return 0.0f; 61 | } else if (t >= 1.0f) { 62 | return 1.0f; 63 | } 64 | // Do a binary search for the correct x to interpolate between. 65 | int startIndex = 0; 66 | int endIndex = mX.length - 1; 67 | while (endIndex - startIndex > 1) { 68 | int midIndex = (startIndex + endIndex) / 2; 69 | if (t < mX[midIndex]) { 70 | endIndex = midIndex; 71 | } else { 72 | startIndex = midIndex; 73 | } 74 | } 75 | final float xRange = mX[endIndex] - mX[startIndex]; 76 | if (xRange == 0) { 77 | return mY[startIndex]; 78 | } 79 | final float tInRange = t - mX[startIndex]; 80 | final float fraction = tInRange / xRange; 81 | final float startY = mY[startIndex]; 82 | final float endY = mY[endIndex]; 83 | return startY + (fraction * (endY - startY)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /keyframe/src/main/java/com/facebook/keyframes/util/ListHelper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.util; 11 | 12 | import java.util.Collections; 13 | import java.util.Comparator; 14 | import java.util.List; 15 | 16 | public class ListHelper { 17 | 18 | /** 19 | * Sorts a list, if it exists. Otherwise just returns. 20 | */ 21 | public static void sort(List list, Comparator comparator) { 22 | if (list == null || list.isEmpty()) { 23 | return; 24 | } 25 | Collections.sort(list, comparator); 26 | } 27 | 28 | /** 29 | * Returns either an immutable copy of a list, or an empty list if the list is null. 30 | * @return An immutable copy of the list, guaranteed to be non-null. 31 | */ 32 | public static List immutableOrEmpty(List list) { 33 | return list == null ? Collections.EMPTY_LIST : Collections.unmodifiableList(list); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /keyframe/src/test/java/com/facebook/keyframes/KFPathTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes; 11 | 12 | import android.graphics.Matrix; 13 | import android.graphics.Path; 14 | 15 | import junit.framework.Assert; 16 | 17 | import org.junit.Test; 18 | import org.mockito.InOrder; 19 | import org.mockito.Mockito; 20 | 21 | public class KFPathTest { 22 | 23 | private class KFPathTestObject { 24 | final Path mockPath; 25 | final KFPath kfPath; 26 | 27 | public KFPathTestObject() { 28 | mockPath = Mockito.mock(Path.class); 29 | kfPath = new KFPath(mockPath, new float[]{0, 0}); 30 | } 31 | } 32 | 33 | @Test 34 | public void testSimpleCommands() { 35 | KFPathTestObject kfPathWrapper = new KFPathTestObject(); 36 | InOrder orderedMock = Mockito.inOrder(kfPathWrapper.mockPath); 37 | 38 | kfPathWrapper.kfPath.isEmpty(); 39 | orderedMock.verify(kfPathWrapper.mockPath).isEmpty(); 40 | 41 | Matrix matrix = new Matrix(); 42 | kfPathWrapper.kfPath.transform(matrix); 43 | orderedMock.verify(kfPathWrapper.mockPath).transform(matrix); 44 | } 45 | 46 | @Test 47 | public void testDrawingCommandsAndLastPoint() { 48 | KFPathTestObject kfPathWrapper = new KFPathTestObject(); 49 | InOrder orderedMock = Mockito.inOrder(kfPathWrapper.mockPath); 50 | 51 | kfPathWrapper.kfPath.moveTo(1.1f, 1.2f); 52 | orderedMock.verify(kfPathWrapper.mockPath).moveTo(1.1f, 1.2f); 53 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[0], 1.1f); 54 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[1], 1.2f); 55 | 56 | kfPathWrapper.kfPath.rMoveTo(2.1f, 2.2f); 57 | orderedMock.verify(kfPathWrapper.mockPath).rMoveTo(2.1f, 2.2f); 58 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[0], 1.1f + 2.1f); 59 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[1], 1.2f + 2.2f); 60 | 61 | kfPathWrapper.kfPath.reset(); 62 | orderedMock.verify(kfPathWrapper.mockPath).reset(); 63 | 64 | kfPathWrapper.kfPath.lineTo(1.1f, 1.2f); 65 | orderedMock.verify(kfPathWrapper.mockPath).lineTo(1.1f, 1.2f); 66 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[0], 1.1f); 67 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[1], 1.2f); 68 | 69 | kfPathWrapper.kfPath.rLineTo(2.1f, 2.2f); 70 | orderedMock.verify(kfPathWrapper.mockPath).rLineTo(2.1f, 2.2f); 71 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[0], 1.1f + 2.1f); 72 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[1], 1.2f + 2.2f); 73 | 74 | kfPathWrapper.kfPath.reset(); 75 | orderedMock.verify(kfPathWrapper.mockPath).reset(); 76 | 77 | kfPathWrapper.kfPath.quadTo(1.1f, 1.2f, 1.3f, 1.4f); 78 | orderedMock.verify(kfPathWrapper.mockPath).quadTo(1.1f, 1.2f, 1.3f, 1.4f); 79 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[0], 1.3f); 80 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[1], 1.4f); 81 | 82 | kfPathWrapper.kfPath.rQuadTo(2.1f, 2.2f, 2.3f, 2.4f); 83 | orderedMock.verify(kfPathWrapper.mockPath).rQuadTo(2.1f, 2.2f, 2.3f, 2.4f); 84 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[0], 1.3f + 2.3f); 85 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[1], 1.4f + 2.4f); 86 | 87 | kfPathWrapper.kfPath.reset(); 88 | orderedMock.verify(kfPathWrapper.mockPath).reset(); 89 | 90 | kfPathWrapper.kfPath.cubicTo(1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f); 91 | orderedMock.verify(kfPathWrapper.mockPath).cubicTo(1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f); 92 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[0], 1.5f); 93 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[1], 1.6f); 94 | 95 | kfPathWrapper.kfPath.rCubicTo(2.1f, 2.2f, 2.3f, 2.4f, 2.5f, 2.6f); 96 | orderedMock.verify(kfPathWrapper.mockPath).rCubicTo(2.1f, 2.2f, 2.3f, 2.4f, 2.5f, 2.6f); 97 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[0], 1.5f + 2.5f); 98 | Assert.assertEquals(kfPathWrapper.kfPath.getLastPoint()[1], 1.6f + 2.6f); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /keyframe/src/test/java/com/facebook/keyframes/model/ModelTests.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.facebook.keyframes.model; 11 | 12 | import org.junit.Assert; 13 | import org.junit.Test; 14 | import org.mockito.Mockito; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Arrays; 18 | 19 | public class ModelTests { 20 | 21 | @Test 22 | public void testMatrixOrderingAnimationGroup() { 23 | KFAnimationGroup.Builder builder = new KFAnimationGroup.Builder(); 24 | builder.groupId = 1; 25 | builder.animations = Arrays.asList( 26 | createDummyAnimationOfType(KFAnimation.PropertyType.ROTATION), 27 | createDummyAnimationOfType(KFAnimation.PropertyType.X_POSITION), 28 | createDummyAnimationOfType(KFAnimation.PropertyType.SCALE)); 29 | KFAnimationGroup group = builder.build(); 30 | 31 | Assert.assertEquals( 32 | KFAnimation.PropertyType.SCALE, 33 | group.getAnimations().get(0).getPropertyType()); 34 | Assert.assertEquals( 35 | KFAnimation.PropertyType.ROTATION, 36 | group.getAnimations().get(1).getPropertyType()); 37 | Assert.assertEquals( 38 | KFAnimation.PropertyType.X_POSITION, 39 | group.getAnimations().get(2).getPropertyType()); 40 | } 41 | 42 | @Test 43 | public void testFeatureAnimationOrderingAndExtraction() { 44 | // Test transform matrix ordering is correct, as well as filtering out stroke_width from matrix 45 | // based animations. 46 | KFFeature.Builder builder = new KFFeature.Builder(); 47 | KFAnimation dummyStrokeWidth = 48 | createDummyAnimationOfType(KFAnimation.PropertyType.STROKE_WIDTH); 49 | builder.featureAnimations = new ArrayList<>(Arrays.asList( 50 | createDummyAnimationOfType(KFAnimation.PropertyType.ROTATION), 51 | dummyStrokeWidth, 52 | createDummyAnimationOfType(KFAnimation.PropertyType.X_POSITION), 53 | createDummyAnimationOfType(KFAnimation.PropertyType.SCALE))); 54 | KFFeature feature = builder.build(); 55 | 56 | Assert.assertEquals(3, feature.mFeatureMatrixAnimations.size(), 0); 57 | Assert.assertEquals( 58 | KFAnimation.PropertyType.SCALE, 59 | feature.mFeatureMatrixAnimations.get(0).getPropertyType()); 60 | Assert.assertEquals( 61 | KFAnimation.PropertyType.ROTATION, 62 | feature.mFeatureMatrixAnimations.get(1).getPropertyType()); 63 | Assert.assertEquals( 64 | KFAnimation.PropertyType.X_POSITION, 65 | feature.mFeatureMatrixAnimations.get(2).getPropertyType()); 66 | Assert.assertEquals( 67 | dummyStrokeWidth, 68 | feature.mStrokeWidthAnimation); 69 | } 70 | 71 | private KFAnimation createDummyAnimationOfType(KFAnimation.PropertyType type) { 72 | KFAnimation animation = Mockito.mock(KFAnimation.class); 73 | Mockito.when(animation.getPropertyType()).thenReturn(type); 74 | return animation; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', 'keyframe' 2 | --------------------------------------------------------------------------------