| package com.airbnb.lottie.samples; |
| |
| import android.Manifest; |
| import android.animation.Animator; |
| import android.animation.ValueAnimator; |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.support.annotation.DrawableRes; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| import android.support.design.widget.Snackbar; |
| import android.support.graphics.drawable.VectorDrawableCompat; |
| import android.support.v4.app.Fragment; |
| import android.support.v4.content.ContextCompat; |
| import android.support.v4.util.Pair; |
| import android.support.v7.app.AppCompatActivity; |
| import android.support.v7.widget.AppCompatSeekBar; |
| import android.support.v7.widget.Toolbar; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.Menu; |
| import android.view.MenuInflater; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.EditText; |
| import android.widget.ImageButton; |
| import android.widget.SeekBar; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import com.airbnb.lottie.LottieAnimationView; |
| import com.airbnb.lottie.LottieComposition; |
| import com.airbnb.lottie.OnCompositionLoadedListener; |
| |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import butterknife.BindView; |
| import butterknife.ButterKnife; |
| import butterknife.OnClick; |
| import okhttp3.Call; |
| import okhttp3.Callback; |
| import okhttp3.OkHttpClient; |
| import okhttp3.Request; |
| import okhttp3.Response; |
| |
| public class AnimationFragment extends Fragment { |
| private static final String TAG = AnimationFragment.class.getSimpleName(); |
| |
| private static final int RC_ASSET = 1337; |
| private static final int RC_FILE = 1338; |
| private static final int RC_URL = 1339; |
| private static final int RC_QR = 1340; |
| private static final int RC_CAMERA = 1341; |
| |
| static final String EXTRA_ANIMATION_NAME = "animation_name"; |
| static final String EXTRA_URL = "json_url"; |
| private static final float SCALE_SLIDER_FACTOR = 50f; |
| |
| static AnimationFragment newInstance() { |
| return new AnimationFragment(); |
| } |
| |
| private final Map<String, String> assetFolders = new HashMap<String, String>() {{ |
| put("WeAccept.json", "Images/WeAccept"); |
| }}; |
| |
| private OkHttpClient client; |
| |
| @BindView(R.id.toolbar) Toolbar toolbar; |
| @BindView(R.id.instructions) ViewGroup instructionsContainer; |
| @BindView(R.id.animation_container) ViewGroup animationContainer; |
| @BindView(R.id.animation_view) LottieAnimationView animationView; |
| @BindView(R.id.seek_bar) AppCompatSeekBar seekBar; |
| @BindView(R.id.scale_seek_bar) AppCompatSeekBar scaleSeekBar; |
| @BindView(R.id.scale_text) TextView scaleTextView; |
| @BindView(R.id.invert_colors) ImageButton invertButton; |
| @BindView(R.id.play_button) ImageButton playButton; |
| @BindView(R.id.loop) ImageButton loopButton; |
| @BindView(R.id.animation_name) TextView animationNameView; |
| @BindView(R.id.qr_code) TextView qrCodeTextView; |
| @BindView(R.id.sample_animations) TextView sampleAnimationsTextView; |
| @BindView(R.id.load_animation) TextView loadAnimationTextView; |
| @BindView(R.id.load_from_json) TextView loadFromJsonTextView; |
| |
| @Nullable |
| @Override |
| public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, |
| @Nullable Bundle savedInstanceState) { |
| View view = inflater.inflate(R.layout.fragment_animation, container, false); |
| ButterKnife.bind(this, view); |
| |
| ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); |
| toolbar.setNavigationIcon(R.drawable.ic_back); |
| toolbar.setNavigationOnClickListener(new View.OnClickListener() { |
| @Override public void onClick(View v) { |
| getFragmentManager().popBackStack(); |
| } |
| }); |
| setHasOptionsMenu(true); |
| postUpdatePlayButtonText(); |
| onLoopChanged(); |
| |
| setDrawableLeft(qrCodeTextView, R.drawable.ic_qr_scan); |
| setDrawableLeft(sampleAnimationsTextView, R.drawable.ic_assets); |
| setDrawableLeft(loadAnimationTextView, R.drawable.ic_file); |
| setDrawableLeft(loadFromJsonTextView, R.drawable.ic_network); |
| |
| animationView.addAnimatorListener(new Animator.AnimatorListener() { |
| @Override public void onAnimationStart(Animator animation) { |
| startRecordingDroppedFrames(); |
| } |
| |
| @Override public void onAnimationEnd(Animator animation) { |
| recordDroppedFrames(); |
| postUpdatePlayButtonText(); |
| } |
| |
| @Override public void onAnimationCancel(Animator animation) { |
| postUpdatePlayButtonText(); |
| } |
| |
| @Override public void onAnimationRepeat(Animator animation) { |
| recordDroppedFrames(); |
| startRecordingDroppedFrames(); |
| } |
| }); |
| animationView.addAnimatorUpdateListener( |
| new ValueAnimator.AnimatorUpdateListener() { |
| @Override public void onAnimationUpdate(ValueAnimator animation) { |
| seekBar.setProgress((int) (animation.getAnimatedFraction() * 100)); |
| } |
| }); |
| |
| seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { |
| @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { |
| if (!animationView.isAnimating()) { |
| animationView.setProgress(progress / 100f); |
| } |
| } |
| |
| @Override public void onStartTrackingTouch(SeekBar seekBar) { |
| } |
| |
| @Override public void onStopTrackingTouch(SeekBar seekBar) { |
| } |
| }); |
| |
| scaleSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { |
| @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { |
| animationView.setScale(progress / SCALE_SLIDER_FACTOR); |
| scaleTextView.setText(String.format(Locale.US, "%.2f", animationView.getScale())); |
| } |
| |
| @Override public void onStartTrackingTouch(SeekBar seekBar) { |
| } |
| |
| @Override public void onStopTrackingTouch(SeekBar seekBar) { |
| } |
| }); |
| |
| return view; |
| } |
| |
| private void setDrawableLeft(TextView textView, @DrawableRes int resId) { |
| Drawable drawable = VectorDrawableCompat.create(getResources(), resId, getActivity().getTheme()); |
| textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null); |
| } |
| |
| @Override public void onStop() { |
| animationView.cancelAnimation(); |
| super.onStop(); |
| } |
| |
| @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { |
| inflater.inflate(R.menu.fragment_animation, menu); |
| } |
| |
| @Override public boolean onOptionsItemSelected(MenuItem item) { |
| if (item.isCheckable()) { |
| item.setChecked(!item.isChecked()); |
| } |
| switch (item.getItemId()) { |
| case R.id.hardware_acceleration: |
| animationView.useExperimentalHardwareAcceleration(item.isChecked()); |
| return true; |
| case R.id.merge_paths: |
| animationView.enableMergePathsForKitKatAndAbove(item.isChecked()); |
| return true; |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { |
| if (resultCode != Activity.RESULT_OK) { |
| return; |
| } |
| |
| switch (requestCode) { |
| case RC_ASSET: |
| final String assetName = data.getStringExtra(EXTRA_ANIMATION_NAME); |
| animationView.setImageAssetsFolder(assetFolders.get(assetName)); |
| LottieComposition.Factory.fromAssetFileName(getContext(), assetName, |
| new OnCompositionLoadedListener() { |
| @Override public void onCompositionLoaded(@Nullable LottieComposition composition) { |
| if (composition == null) { |
| onLoadError(); |
| return; |
| } |
| setComposition(composition, assetName); |
| } |
| }); |
| break; |
| case RC_FILE: |
| onFileLoaded(data.getData()); |
| break; |
| case RC_URL: |
| |
| break; |
| case RC_QR: |
| loadUrl(data.getExtras().getString(EXTRA_URL)); |
| break; |
| } |
| } |
| |
| private void setComposition(LottieComposition composition, String name) { |
| if (composition.hasImages() && TextUtils.isEmpty(animationView.getImageAssetsFolder())) { |
| //noinspection ConstantConditions |
| Snackbar.make( |
| getView(), |
| "This animation has images and no image folder was set", |
| Snackbar.LENGTH_LONG).show(); |
| return; |
| } |
| instructionsContainer.setVisibility(View.GONE); |
| seekBar.setProgress(0); |
| animationView.setComposition(composition); |
| animationNameView.setText(name); |
| scaleTextView.setText(String.format(Locale.US, "%.2f", animationView.getScale())); |
| scaleSeekBar.setProgress((int) (animationView.getScale() * SCALE_SLIDER_FACTOR)); |
| |
| } |
| |
| @OnClick(R.id.play_button) |
| void onPlayClicked() { |
| if (animationView.isAnimating()) { |
| animationView.pauseAnimation(); |
| postUpdatePlayButtonText(); |
| } else { |
| if (animationView.getProgress() == 1f) { |
| animationView.setProgress(0f); |
| } |
| animationView.resumeAnimation(); |
| postUpdatePlayButtonText(); |
| } |
| } |
| |
| @OnClick(R.id.loop) |
| void onLoopChanged() { |
| loopButton.setActivated(!loopButton.isActivated()); |
| animationView.loop(loopButton.isActivated()); |
| } |
| |
| @Override |
| public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, |
| @NonNull int[] grantResults) { |
| if (requestCode == RC_CAMERA && grantResults.length > 0 && grantResults[0] == PackageManager |
| .PERMISSION_GRANTED) { |
| startActivityForResult(new Intent(getContext(), QRScanActivity.class), RC_QR); |
| } else { |
| Toast.makeText(getContext(), R.string.permission_required, Toast.LENGTH_LONG).show(); |
| } |
| |
| } |
| |
| @OnClick(R.id.qrscan) |
| void onQRScanClicked() { |
| animationView.cancelAnimation(); |
| if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) != |
| PackageManager.PERMISSION_GRANTED) { |
| |
| requestPermissions(new String[]{Manifest.permission.CAMERA}, RC_CAMERA); |
| |
| } else { |
| startActivityForResult(new Intent(getContext(), QRScanActivity.class), RC_QR); |
| } |
| } |
| |
| @OnClick(R.id.restart) |
| void onRestartClicked() { |
| boolean restart = animationView.isAnimating(); |
| animationView.cancelAnimation(); |
| animationView.setProgress(0f); |
| if (restart) { |
| animationView.playAnimation(); |
| } |
| } |
| |
| @OnClick(R.id.invert_colors) |
| void onInvertClicked() { |
| animationContainer.setActivated(!animationContainer.isActivated()); |
| invertButton.setActivated(animationContainer.isActivated()); |
| } |
| |
| @OnClick(R.id.load_asset) |
| void onLoadAssetClicked() { |
| animationView.cancelAnimation(); |
| android.support.v4.app.DialogFragment assetFragment = ChooseAssetDialogFragment.newInstance(); |
| assetFragment.setTargetFragment(this, RC_ASSET); |
| assetFragment.show(getFragmentManager(), "assets"); |
| } |
| |
| @OnClick(R.id.load_file) |
| void onLoadFileClicked() { |
| animationView.cancelAnimation(); |
| Intent intent = new Intent(Intent.ACTION_GET_CONTENT); |
| intent.setType("*/*"); |
| intent.addCategory(Intent.CATEGORY_OPENABLE); |
| |
| try { |
| startActivityForResult(Intent.createChooser(intent, "Select a JSON file"), RC_FILE); |
| } catch (android.content.ActivityNotFoundException ex) { |
| // Potentially direct the user to the Market with a Dialog |
| Toast.makeText(getContext(), "Please install a File Manager.", Toast.LENGTH_SHORT).show(); |
| } |
| } |
| |
| @OnClick(R.id.load_url_or_json) |
| void onLoadUrlOrJsonClicked() { |
| animationView.cancelAnimation(); |
| final EditText urlOrJsonView = new EditText(getContext()); |
| new AlertDialog.Builder(getContext()) |
| .setTitle("Enter a URL or JSON string") |
| .setView(urlOrJsonView) |
| .setPositiveButton("Load", new DialogInterface.OnClickListener() { |
| @Override public void onClick(DialogInterface dialog, int which) { |
| loadUrlOrJson(urlOrJsonView.getText().toString()); |
| } |
| }) |
| .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { |
| @Override public void onClick(DialogInterface dialog, int which) { |
| dialog.dismiss(); |
| } |
| }) |
| .show(); |
| } |
| |
| private void postUpdatePlayButtonText() { |
| new Handler().post(new Runnable() { |
| @Override public void run() { |
| updatePlayButtonText(); |
| } |
| }); |
| } |
| |
| private void updatePlayButtonText() { |
| playButton.setActivated(animationView.isAnimating()); |
| } |
| |
| private void onFileLoaded(final Uri uri) { |
| InputStream fis; |
| |
| try { |
| switch (uri.getScheme()) { |
| case "file": |
| fis = new FileInputStream(uri.getPath()); |
| break; |
| case "content": |
| fis = getContext().getContentResolver().openInputStream(uri); |
| break; |
| default: |
| onLoadError(); |
| return; |
| } |
| } catch (FileNotFoundException e) { |
| onLoadError(); |
| return; |
| } |
| |
| LottieComposition.Factory |
| .fromInputStream(getContext(), fis, new OnCompositionLoadedListener() { |
| @Override public void onCompositionLoaded(@Nullable LottieComposition composition) { |
| if (composition == null) { |
| onLoadError(); |
| return; |
| } |
| setComposition(composition, uri.getPath()); |
| } |
| }); |
| } |
| |
| private void loadUrlOrJson(String text) { |
| if (text.charAt(0) == '{') { |
| // Assume JSON |
| loadJsonString(text); |
| return; |
| } |
| loadUrl(text); |
| } |
| |
| private void loadJsonString(String jsonString) { |
| try { |
| JSONObject json = new JSONObject(jsonString); |
| LottieComposition.Factory |
| .fromJson(getResources(), json, new OnCompositionLoadedListener() { |
| @Override public void onCompositionLoaded(@Nullable LottieComposition composition) { |
| if (composition == null) { |
| onLoadError(); |
| return; |
| } |
| setComposition(composition, "Animation"); |
| } |
| }); |
| } catch (JSONException e) { |
| onLoadError(); |
| } |
| } |
| |
| private void loadUrl(String url) { |
| Request request; |
| try { |
| request = new Request.Builder() |
| .url(url) |
| .build(); |
| } catch (IllegalArgumentException e) { |
| onLoadError(); |
| return; |
| } |
| |
| |
| if (client == null) { |
| client = new OkHttpClient(); |
| } |
| client.newCall(request).enqueue(new Callback() { |
| @Override public void onFailure(Call call, IOException e) { |
| onLoadError(); |
| } |
| |
| @Override public void onResponse(Call call, Response response) throws IOException { |
| if (!response.isSuccessful()) { |
| onLoadError(); |
| } |
| |
| loadJsonString(response.body().string()); |
| } |
| }); |
| } |
| |
| private void onLoadError() { |
| //noinspection ConstantConditions |
| Snackbar.make(getView(), "Failed to load animation", Snackbar.LENGTH_LONG).show(); |
| } |
| |
| private void startRecordingDroppedFrames() { |
| getApplication().startRecordingDroppedFrames(); |
| } |
| |
| private void recordDroppedFrames() { |
| Pair<Integer, Long> droppedFrames = getApplication().stopRecordingDroppedFrames(); |
| Log.d(TAG, "Dropped frames: " + droppedFrames.first); |
| } |
| |
| private ILottieApplication getApplication() { |
| return (ILottieApplication) getActivity().getApplication(); |
| } |
| } |