From 7f90a56d127d887b594d7372a6fec07b1e54dc2d Mon Sep 17 00:00:00 2001 From: MasayukiSuda Date: Tue, 23 Jul 2019 23:20:38 +0900 Subject: [PATCH] add fill mode custom sample --- build.gradle | 2 +- sample/build.gradle | 47 ++-- sample/src/main/AndroidManifest.xml | 20 +- .../com/daasuu/sample/BasicUsageActivity.java | 225 ++++++++++++++++++ .../daasuu/sample/FillModeCustomActivity.java | 187 +++++++++++++++ .../java/com/daasuu/sample/MainActivity.java | 207 +--------------- .../com/daasuu/sample/MovieListActivity.java | 57 +++++ .../sample/widget/AllGestureDetector.java | 123 ++++++++++ .../sample/widget/DragGestureDetector.java | 139 +++++++++++ .../widget/GesturePlayerTextureView.java | 78 ++++++ .../sample/widget/PinchGestureDetector.java | 112 +++++++++ .../sample/widget/PlayerTextureView.java | 110 +++++++++ .../daasuu/sample/widget/PortraitView.java | 29 +++ .../sample/widget/RotateGestureDetector.java | 120 ++++++++++ .../main/res/drawable/rect_white_drawable.xml | 11 + .../main/res/layout/activity_basic_usage.xml | 97 ++++++++ .../res/layout/activity_fill_mode_custom.xml | 31 +++ sample/src/main/res/layout/activity_main.xml | 108 ++------- .../main/res/layout/activity_movie_list.xml | 16 ++ sample/src/main/res/values/strings.xml | 3 + 20 files changed, 1404 insertions(+), 318 deletions(-) create mode 100644 sample/src/main/java/com/daasuu/sample/BasicUsageActivity.java create mode 100644 sample/src/main/java/com/daasuu/sample/FillModeCustomActivity.java create mode 100644 sample/src/main/java/com/daasuu/sample/MovieListActivity.java create mode 100644 sample/src/main/java/com/daasuu/sample/widget/AllGestureDetector.java create mode 100644 sample/src/main/java/com/daasuu/sample/widget/DragGestureDetector.java create mode 100644 sample/src/main/java/com/daasuu/sample/widget/GesturePlayerTextureView.java create mode 100644 sample/src/main/java/com/daasuu/sample/widget/PinchGestureDetector.java create mode 100644 sample/src/main/java/com/daasuu/sample/widget/PlayerTextureView.java create mode 100644 sample/src/main/java/com/daasuu/sample/widget/PortraitView.java create mode 100644 sample/src/main/java/com/daasuu/sample/widget/RotateGestureDetector.java create mode 100644 sample/src/main/res/drawable/rect_white_drawable.xml create mode 100644 sample/src/main/res/layout/activity_basic_usage.xml create mode 100644 sample/src/main/res/layout/activity_fill_mode_custom.xml create mode 100644 sample/src/main/res/layout/activity_movie_list.xml diff --git a/build.gradle b/build.gradle index 82e69c2..aa6eb41 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenLocal() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.android.tools.build:gradle:3.4.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/sample/build.gradle b/sample/build.gradle index acd184c..82a17f9 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,34 +1,37 @@ apply plugin: 'com.android.application' android { - compileSdkVersion COMPILE_SDK_VERSION as int - buildToolsVersion BUILD_TOOLS_VERSION - defaultConfig { - applicationId "com.daasuu.mp4composer" - minSdkVersion COMPILE_MIN_SDK_VERSION as int - targetSdkVersion COMPILE_SDK_VERSION as int + compileSdkVersion COMPILE_SDK_VERSION as int + buildToolsVersion BUILD_TOOLS_VERSION + defaultConfig { + applicationId "com.daasuu.mp4composer" + minSdkVersion COMPILE_MIN_SDK_VERSION as int + targetSdkVersion COMPILE_SDK_VERSION as int - versionCode 1 - versionName "1.0" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:28.0.0' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:28.0.0' + + implementation 'com.github.bumptech.glide:glide:3.8.0' - implementation 'com.github.bumptech.glide:glide:3.8.0' + implementation project(':mp4compose') - implementation project(':mp4compose') + implementation 'com.google.android.exoplayer:exoplayer-core:2.10.2' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index c82e480..2b113bb 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -1,18 +1,13 @@ - - - - - - - + + @@ -24,7 +19,16 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - + + + + diff --git a/sample/src/main/java/com/daasuu/sample/BasicUsageActivity.java b/sample/src/main/java/com/daasuu/sample/BasicUsageActivity.java new file mode 100644 index 0000000..46b22f0 --- /dev/null +++ b/sample/src/main/java/com/daasuu/sample/BasicUsageActivity.java @@ -0,0 +1,225 @@ +package com.daasuu.sample; + +import android.app.AlertDialog; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.provider.MediaStore; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.Toast; + +import com.daasuu.mp4compose.FillMode; +import com.daasuu.mp4compose.composer.Mp4Composer; +import com.daasuu.mp4compose.filter.GlFilter; +import com.daasuu.mp4compose.filter.GlFilterGroup; +import com.daasuu.mp4compose.filter.GlMonochromeFilter; +import com.daasuu.mp4compose.filter.GlVignetteFilter; +import com.daasuu.sample.video.VideoItem; +import com.daasuu.sample.video.VideoListAdapter; +import com.daasuu.sample.video.VideoLoadListener; +import com.daasuu.sample.video.VideoLoader; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +public class BasicUsageActivity extends AppCompatActivity { + + private VideoItem videoItem = null; + + private static final String TAG = "SAMPLE"; + + private Mp4Composer mp4Composer; + + private CheckBox muteCheckBox; + private CheckBox flipVerticalCheckBox; + private CheckBox flipHorizontalCheckBox; + + private String videoPath; + private AlertDialog filterDialog; + private GlFilter glFilter = new GlFilterGroup(new GlMonochromeFilter(), new GlVignetteFilter()); + + public static void startActivity(Context context) { + Intent intent = new Intent(context, BasicUsageActivity.class); + context.startActivity(intent); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_basic_usage); + + muteCheckBox = findViewById(R.id.mute_check_box); + flipVerticalCheckBox = findViewById(R.id.flip_vertical_check_box); + flipHorizontalCheckBox = findViewById(R.id.flip_horizontal_check_box); + + findViewById(R.id.start_codec_button).setOnClickListener(v -> { + v.setEnabled(false); + startCodec(); + }); + + findViewById(R.id.cancel_button).setOnClickListener(v -> { + if (mp4Composer != null) { + mp4Composer.cancel(); + } + }); + + findViewById(R.id.start_play_movie).setOnClickListener(v -> { + Uri uri = Uri.parse(videoPath); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.setDataAndType(uri, "video/mp4"); + startActivity(intent); + }); + + findViewById(R.id.btn_filter).setOnClickListener(v -> { + if (filterDialog == null) { + + AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext()); + builder.setTitle("Choose a filter"); + builder.setOnDismissListener(dialog -> { + filterDialog = null; + }); + + final FilterType[] filters = FilterType.values(); + CharSequence[] charList = new CharSequence[filters.length]; + for (int i = 0, n = filters.length; i < n; i++) { + charList[i] = filters[i].name(); + } + builder.setItems(charList, (dialog, item) -> { + changeFilter(filters[item]); + }); + filterDialog = builder.show(); + } else { + filterDialog.dismiss(); + } + }); + + } + + private void changeFilter(FilterType filter) { + glFilter = null; + glFilter = FilterType.createGlFilter(filter, this); + Button button = findViewById(R.id.btn_filter); + button.setText("Filter : " + filter.name()); + } + + @Override + protected void onResume() { + super.onResume(); + VideoLoader videoLoader = new VideoLoader(getApplicationContext()); + videoLoader.loadDeviceVideos(new VideoLoadListener() { + @Override + public void onVideoLoaded(final List items) { + + ListView lv = findViewById(R.id.video_list); + VideoListAdapter adapter = new VideoListAdapter(getApplicationContext(), R.layout.row_video_list, items); + lv.setAdapter(adapter); + + lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + videoItem = null; + videoItem = items.get(position); + findViewById(R.id.start_codec_button).setEnabled(true); + } + }); + } + + @Override + public void onFailed(Exception e) { + e.printStackTrace(); + } + }); + } + + private void startCodec() { + videoPath = getVideoFilePath(); + + final ProgressBar progressBar = findViewById(R.id.progress_bar); + progressBar.setMax(100); + + + mp4Composer = null; + mp4Composer = new Mp4Composer(videoItem.getPath(), videoPath) + // .rotation(Rotation.ROTATION_270) + .size(720, 720) + .fillMode(FillMode.PRESERVE_ASPECT_FIT) + .filter(glFilter) + .mute(muteCheckBox.isChecked()) + .flipHorizontal(flipHorizontalCheckBox.isChecked()) + .flipVertical(flipVerticalCheckBox.isChecked()) + .listener(new Mp4Composer.Listener() { + @Override + public void onProgress(double progress) { + Log.d(TAG, "onProgress = " + progress); + runOnUiThread(() -> progressBar.setProgress((int) (progress * 100))); + } + + @Override + public void onCompleted() { + Log.d(TAG, "onCompleted()"); + exportMp4ToGallery(getApplicationContext(), videoPath); + runOnUiThread(() -> { + progressBar.setProgress(100); + findViewById(R.id.start_codec_button).setEnabled(true); + findViewById(R.id.start_play_movie).setEnabled(true); + Toast.makeText(BasicUsageActivity.this, "codec complete path =" + videoPath, Toast.LENGTH_SHORT).show(); + }); + } + + @Override + public void onCanceled() { + + } + + @Override + public void onFailed(Exception exception) { + Log.d(TAG, "onFailed()"); + } + }) + .start(); + + + } + + + public File getAndroidMoviesFolder() { + return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES); + } + + public String getVideoFilePath() { + return getAndroidMoviesFolder().getAbsolutePath() + "/" + new SimpleDateFormat("yyyyMM_dd-HHmmss").format(new Date()) + "filter_apply.mp4"; + } + + /** + * ギャラリーにエクスポート + * + * @param filePath + * @return The video MediaStore URI + */ + public static void exportMp4ToGallery(Context context, String filePath) { + // ビデオのメタデータを作成する + final ContentValues values = new ContentValues(2); + values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); + values.put(MediaStore.Video.Media.DATA, filePath); + // MediaStoreに登録 + context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + values); + context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, + Uri.parse("file://" + filePath))); + } + + +} diff --git a/sample/src/main/java/com/daasuu/sample/FillModeCustomActivity.java b/sample/src/main/java/com/daasuu/sample/FillModeCustomActivity.java new file mode 100644 index 0000000..ef69b6c --- /dev/null +++ b/sample/src/main/java/com/daasuu/sample/FillModeCustomActivity.java @@ -0,0 +1,187 @@ +package com.daasuu.sample; + +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.graphics.Point; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.provider.MediaStore; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.util.Size; +import android.view.Display; +import android.view.Gravity; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.Toast; + +import com.daasuu.mp4compose.FillMode; +import com.daasuu.mp4compose.FillModeCustomItem; +import com.daasuu.mp4compose.composer.Mp4Composer; +import com.daasuu.sample.widget.GesturePlayerTextureView; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class FillModeCustomActivity extends AppCompatActivity { + + private final static String PATH_ARG = "PATH_ARG"; + private static final String TAG = "SAMPLE"; + + private String srcPath; + private float baseWidthSize; + private GesturePlayerTextureView playerTextureView; + + public static void startActivity(Context context, String path) { + Intent intent = new Intent(context, FillModeCustomActivity.class); + intent.putExtra(PATH_ARG, path); + context.startActivity(intent); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_fill_mode_custom); + + if (getIntent() == null || getIntent().getStringExtra(PATH_ARG) == null) { + finish(); + return; + } + srcPath = getIntent().getStringExtra(PATH_ARG); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + + + findViewById(R.id.btn_rotate).setOnClickListener((v) -> { + playerTextureView.updateRotate(); + }); + findViewById(R.id.btn_codec).setOnClickListener((v) -> { + codec(); + }); + + + FrameLayout frameLayout = findViewById(R.id.layout_crop_change); + playerTextureView = new GesturePlayerTextureView(getApplicationContext(), srcPath); + + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); + layoutParams.gravity = Gravity.CENTER; + + playerTextureView.setLayoutParams(layoutParams); + baseWidthSize = (getWindowHeight(this) - dp2px(192, this)) / 16f * 9; + playerTextureView.setBaseWidthSize(baseWidthSize); + + frameLayout.addView(playerTextureView, 1); + } + + private void codec() { + + Size resolution = getVideoResolution(srcPath); + + FillModeCustomItem fillModeCustomItem = new FillModeCustomItem( + playerTextureView.getScaleX(), + playerTextureView.getRotation(), + playerTextureView.getTranslationX() / baseWidthSize * 2f, + playerTextureView.getTranslationY() / (baseWidthSize / 9f * 16) * 2f, + resolution.getWidth(), + resolution.getHeight() + ); + + final String videoPath = getVideoFilePath(); + new Mp4Composer(srcPath, videoPath) + .size(720, 1280) + .fillMode(FillMode.CUSTOM) + .customFillMode(fillModeCustomItem) + .listener(new Mp4Composer.Listener() { + @Override + public void onProgress(double progress) { + Log.d(TAG, "onProgress = " + progress); + //runOnUiThread(() -> progressBar.setProgress((int) (progress * 100))); + } + + @Override + public void onCompleted() { + Log.d(TAG, "onCompleted()"); + exportMp4ToGallery(getApplicationContext(), videoPath); + runOnUiThread(() -> { + //progressBar.setProgress(100); + //findViewById(R.id.start_codec_button).setEnabled(true); + //findViewById(R.id.start_play_movie).setEnabled(true); + Toast.makeText(FillModeCustomActivity.this, "codec complete path =" + videoPath, Toast.LENGTH_SHORT).show(); + }); + } + + @Override + public void onCanceled() { + + } + + @Override + public void onFailed(Exception exception) { + Log.d(TAG, "onFailed()"); + } + }) + .start(); + + } + + + public static int getWindowHeight(Context context) { + Display disp = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + Point size = new Point(); + disp.getSize(size); + return size.y; + } + + public static int dp2px(float dpValue, final Context context) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + public File getAndroidMoviesFolder() { + return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES); + } + + public String getVideoFilePath() { + return getAndroidMoviesFolder().getAbsolutePath() + "/" + new SimpleDateFormat("yyyyMM_dd-HHmmss").format(new Date()) + "filter_apply.mp4"; + } + + public Size getVideoResolution(String path) { + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(path); + int width = Integer.valueOf( + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) + ); + int height = Integer.valueOf( + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) + ); + retriever.release(); + int rotation = getVideoRotation(path); + if (rotation == 90 || rotation == 270) { + return new Size(height, width); + } + return new Size(width, height); + } + + + public int getVideoRotation(String videoFilePath) { + MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever(); + mediaMetadataRetriever.setDataSource(videoFilePath); + String orientation = mediaMetadataRetriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION + ); + return Integer.valueOf(orientation); + } + + public static void exportMp4ToGallery(Context context, String filePath) { + final ContentValues values = new ContentValues(2); + values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); + values.put(MediaStore.Video.Media.DATA, filePath); + context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + values); + context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, + Uri.parse("file://" + filePath))); + } +} diff --git a/sample/src/main/java/com/daasuu/sample/MainActivity.java b/sample/src/main/java/com/daasuu/sample/MainActivity.java index 61f4575..6a9bac5 100644 --- a/sample/src/main/java/com/daasuu/sample/MainActivity.java +++ b/sample/src/main/java/com/daasuu/sample/MainActivity.java @@ -1,235 +1,42 @@ package com.daasuu.sample; import android.Manifest; -import android.app.AlertDialog; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Environment; -import android.provider.MediaStore; import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.View; -import android.widget.AdapterView; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.ListView; -import android.widget.ProgressBar; import android.widget.Toast; -import com.daasuu.mp4compose.FillMode; -import com.daasuu.mp4compose.composer.Mp4Composer; -import com.daasuu.mp4compose.filter.GlFilter; -import com.daasuu.mp4compose.filter.GlFilterGroup; -import com.daasuu.mp4compose.filter.GlMonochromeFilter; -import com.daasuu.mp4compose.filter.GlVignetteFilter; -import com.daasuu.sample.video.VideoItem; -import com.daasuu.sample.video.VideoListAdapter; -import com.daasuu.sample.video.VideoLoadListener; -import com.daasuu.sample.video.VideoLoader; - -import java.io.File; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; - public class MainActivity extends AppCompatActivity { - private VideoLoader videoLoader; - - private VideoItem videoItem = null; - - private static final String TAG = "SAMPLE"; - private static final int PERMISSION_REQUEST_CODE = 88888; - private Mp4Composer mp4Composer; - private Bitmap bitmap; - - private CheckBox muteCheckBox; - private CheckBox flipVerticalCheckBox; - private CheckBox flipHorizontalCheckBox; - - private String videoPath; - private AlertDialog filterDialog; - private GlFilter glFilter = new GlFilterGroup(new GlMonochromeFilter(), new GlVignetteFilter()); - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - muteCheckBox = findViewById(R.id.mute_check_box); - flipVerticalCheckBox = findViewById(R.id.flip_vertical_check_box); - flipHorizontalCheckBox = findViewById(R.id.flip_horizontal_check_box); - - findViewById(R.id.start_codec_button).setOnClickListener(v -> { - v.setEnabled(false); - startCodec(); - }); - - findViewById(R.id.cancel_button).setOnClickListener(v -> { - if (mp4Composer != null) { - mp4Composer.cancel(); + findViewById(R.id.button).setOnClickListener(v -> { + if (checkPermission()) { + BasicUsageActivity.startActivity(MainActivity.this); } }); - - findViewById(R.id.start_play_movie).setOnClickListener(v -> { - Uri uri = Uri.parse(videoPath); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - intent.setDataAndType(uri, "video/mp4"); - startActivity(intent); - }); - - findViewById(R.id.btn_filter).setOnClickListener(v -> { - if (filterDialog == null) { - - AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext()); - builder.setTitle("Choose a filter"); - builder.setOnDismissListener(dialog -> { - filterDialog = null; - }); - - final FilterType[] filters = FilterType.values(); - CharSequence[] charList = new CharSequence[filters.length]; - for (int i = 0, n = filters.length; i < n; i++) { - charList[i] = filters[i].name(); - } - builder.setItems(charList, (dialog, item) -> { - changeFilter(filters[item]); - }); - filterDialog = builder.show(); - } else { - filterDialog.dismiss(); + findViewById(R.id.button2).setOnClickListener(v -> { + if (checkPermission()) { + MovieListActivity.startActivity(MainActivity.this); } }); - - bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lookup_sample); } - private void changeFilter(FilterType filter) { - glFilter = null; - glFilter = FilterType.createGlFilter(filter, this); - Button button = findViewById(R.id.btn_filter); - button.setText("Filter : " + filter.name()); - } @Override protected void onResume() { super.onResume(); - if (checkPermission()) { - videoLoader = new VideoLoader(getApplicationContext()); - videoLoader.loadDeviceVideos(new VideoLoadListener() { - @Override - public void onVideoLoaded(final List items) { - - ListView lv = findViewById(R.id.video_list); - VideoListAdapter adapter = new VideoListAdapter(getApplicationContext(), R.layout.row_video_list, items); - lv.setAdapter(adapter); - - lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - videoItem = null; - videoItem = items.get(position); - findViewById(R.id.start_codec_button).setEnabled(true); - } - }); - } + checkPermission(); - @Override - public void onFailed(Exception e) { - e.printStackTrace(); - } - }); - } } - private void startCodec() { - videoPath = getVideoFilePath(); - - final ProgressBar progressBar = findViewById(R.id.progress_bar); - progressBar.setMax(100); - - - mp4Composer = null; - mp4Composer = new Mp4Composer(videoItem.getPath(), videoPath) - // .rotation(Rotation.ROTATION_270) - //.size(720, 1280) - .fillMode(FillMode.PRESERVE_ASPECT_FIT) - .filter(glFilter) - .mute(muteCheckBox.isChecked()) - .flipHorizontal(flipHorizontalCheckBox.isChecked()) - .flipVertical(flipVerticalCheckBox.isChecked()) - .listener(new Mp4Composer.Listener() { - @Override - public void onProgress(double progress) { - Log.d(TAG, "onProgress = " + progress); - runOnUiThread(() -> progressBar.setProgress((int) (progress * 100))); - } - - @Override - public void onCompleted() { - Log.d(TAG, "onCompleted()"); - exportMp4ToGallery(getApplicationContext(), videoPath); - runOnUiThread(() -> { - progressBar.setProgress(100); - findViewById(R.id.start_codec_button).setEnabled(true); - findViewById(R.id.start_play_movie).setEnabled(true); - Toast.makeText(MainActivity.this, "codec complete path =" + videoPath, Toast.LENGTH_SHORT).show(); - }); - } - - @Override - public void onCanceled() { - - } - - @Override - public void onFailed(Exception exception) { - Log.d(TAG, "onFailed()"); - } - }) - .start(); - - - } - - - public File getAndroidMoviesFolder() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES); - } - - public String getVideoFilePath() { - return getAndroidMoviesFolder().getAbsolutePath() + "/" + new SimpleDateFormat("yyyyMM_dd-HHmmss").format(new Date()) + "filter_apply.mp4"; - } - - /** - * ギャラリーにエクスポート - * - * @param filePath - * @return The video MediaStore URI - */ - public static void exportMp4ToGallery(Context context, String filePath) { - // ビデオのメタデータを作成する - final ContentValues values = new ContentValues(2); - values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); - values.put(MediaStore.Video.Media.DATA, filePath); - // MediaStoreに登録 - context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, - values); - context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, - Uri.parse("file://" + filePath))); - } - - private boolean checkPermission() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; diff --git a/sample/src/main/java/com/daasuu/sample/MovieListActivity.java b/sample/src/main/java/com/daasuu/sample/MovieListActivity.java new file mode 100644 index 0000000..a2ed244 --- /dev/null +++ b/sample/src/main/java/com/daasuu/sample/MovieListActivity.java @@ -0,0 +1,57 @@ +package com.daasuu.sample; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; + +import com.daasuu.sample.video.VideoItem; +import com.daasuu.sample.video.VideoListAdapter; +import com.daasuu.sample.video.VideoLoadListener; +import com.daasuu.sample.video.VideoLoader; + +import java.util.List; + +public class MovieListActivity extends AppCompatActivity { + + public static void startActivity(Context context) { + Intent intent = new Intent(context, MovieListActivity.class); + context.startActivity(intent); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_movie_list); + } + + @Override + protected void onResume() { + super.onResume(); + VideoLoader videoLoader = new VideoLoader(getApplicationContext()); + videoLoader.loadDeviceVideos(new VideoLoadListener() { + @Override + public void onVideoLoaded(final List items) { + + ListView lv = findViewById(R.id.video_list); + VideoListAdapter adapter = new VideoListAdapter(getApplicationContext(), R.layout.row_video_list, items); + lv.setAdapter(adapter); + lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + FillModeCustomActivity.startActivity(MovieListActivity.this, items.get(position).getPath()); + } + }); + } + + @Override + public void onFailed(Exception e) { + e.printStackTrace(); + } + }); + } + +} diff --git a/sample/src/main/java/com/daasuu/sample/widget/AllGestureDetector.java b/sample/src/main/java/com/daasuu/sample/widget/AllGestureDetector.java new file mode 100644 index 0000000..4945ebb --- /dev/null +++ b/sample/src/main/java/com/daasuu/sample/widget/AllGestureDetector.java @@ -0,0 +1,123 @@ +package com.daasuu.sample.widget; + +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.View; + +class AllGestureDetector implements DragGestureDetector.DragGestureListener, RotateGestureDetector.RotateGestureListener, PinchGestureDetector.PinchGestureListener { + + private static final float DEFAULT_LIMIT_SCALE_MAX = 2.7f; + private static final float DEFAULT_LIMIT_SCALE_MIN = 0.5f; + + private float limitScaleMax = DEFAULT_LIMIT_SCALE_MAX; + private float limitScaleMin = DEFAULT_LIMIT_SCALE_MIN; + + private float scaleFactor = 1.0f; + + private final RotateGestureDetector rotateGestureDetector; + private final DragGestureDetector dragGestureDetector; + private final PinchGestureDetector pinchGestureDetector; + private final View view; + + private float angle = 0f; + private boolean rotateFlag = true; + + private MoveDragXYListener moveDragXYListener; + + AllGestureDetector(View view) { + dragGestureDetector = new DragGestureDetector(this); + rotateGestureDetector = new RotateGestureDetector(this); + pinchGestureDetector = new PinchGestureDetector(this); + this.view = view; + } + + void onTouch(MotionEvent event) { + if (rotateFlag) { + rotateGestureDetector.onTouchEvent(event); + } + dragGestureDetector.onTouchEvent(event); + pinchGestureDetector.onTouchEvent(event); + } + + void noRotate() { + rotateFlag = false; + } + + public void setMoveDragXYListener(MoveDragXYListener moveDragXYListener) { + this.moveDragXYListener = moveDragXYListener; + } + + void updateAngle() { + this.angle = view.getRotation(); + } + + public void setLimitScaleMax(float limit) { + this.limitScaleMax = limit; + } + + void setLimitScaleMin(float limit) { + this.limitScaleMin = limit; + } + + + @Override + public void onPinchGestureListener(float scale) { + float tmpScale = scaleFactor * scale; + + if (limitScaleMin <= tmpScale && tmpScale <= limitScaleMax) { + scaleFactor = tmpScale; + view.setScaleX(scaleFactor); + view.setScaleY(scaleFactor); + } + + } + + + // rotate + @Override + public void onRotation(float deltaAngle) { + angle += deltaAngle; + view.setRotation(view.getRotation() + deltaAngle); + } + + + @Override + synchronized public void onDragGestureListener(float deltaX, float deltaY) { + + // touch move + + float dx = deltaX; + float dy = deltaY; + PointF pf = createRotatePointF(0, 0, angle, dx, dy); + + dx = pf.x; + dy = pf.y; + + float x = view.getX() + dx * scaleFactor; + float y = view.getY() + dy * scaleFactor; + + if (moveDragXYListener != null) { + moveDragXYListener.onMove(x, y); + } + + view.setX(x); + view.setY(y); + } + + + private static PointF createRotatePointF(float centerX, float centerY, float angle, float x, float y) { + + double rad = Math.toRadians(angle); + + float resultX = (float) ((x - centerX) * Math.cos(rad) - (y - centerY) * Math.sin(rad) + centerX); + float resultY = (float) ((x - centerX) * Math.sin(rad) + (y - centerY) * Math.cos(rad) + centerY); + + return new PointF(resultX, resultY); + } + + public interface MoveDragXYListener { + void onMove(float x, float y); + } + +} + diff --git a/sample/src/main/java/com/daasuu/sample/widget/DragGestureDetector.java b/sample/src/main/java/com/daasuu/sample/widget/DragGestureDetector.java new file mode 100644 index 0000000..62a9b11 --- /dev/null +++ b/sample/src/main/java/com/daasuu/sample/widget/DragGestureDetector.java @@ -0,0 +1,139 @@ +package com.daasuu.sample.widget; + +import android.util.Log; +import android.view.MotionEvent; + +import java.util.HashMap; + +class DragGestureDetector { + + private static final String TAG = DragGestureDetector.class.getName(); + + private int originalIndex; + + private HashMap pointMap = new HashMap<>(); + + private DragGestureListener dragGestureListener; + + public interface DragGestureListener { + void onDragGestureListener(float deltaX, float deltaY); + } + + DragGestureDetector(DragGestureListener dragGestureListener) { + this.dragGestureListener = dragGestureListener; + pointMap.put(0, createPoint(0.f, 0.f)); + originalIndex = 0; + } + + synchronized boolean onTouchEvent(MotionEvent event) { + + if (event.getPointerCount() >= 3) { + return false; + } + + float eventX = event.getX(originalIndex); + float eventY = event.getY(originalIndex); + + int action = event.getAction() & MotionEvent.ACTION_MASK; + int actionPointer = event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK; + + switch (action) { + case MotionEvent.ACTION_DOWN: { + + // 最初のpointしか来ない + + TouchPoint downPoint = pointMap.get(0); + if (downPoint != null) { + downPoint.setXY(eventX, eventY); + } else { + downPoint = createPoint(eventX, eventY); + pointMap.put(0, downPoint); + } + + originalIndex = 0; + + break; + } + case MotionEvent.ACTION_MOVE: { + + TouchPoint originalPoint = pointMap.get(originalIndex); + if (originalPoint != null) { + float deltaX = eventX - originalPoint.x; + float deltaY = eventY - originalPoint.y; + + if (dragGestureListener != null) {// && (count < 2)) { + dragGestureListener.onDragGestureListener(deltaX, deltaY); + } + } + + break; + } + case MotionEvent.ACTION_POINTER_DOWN: { + + int downId = actionPointer >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + + float multiTouchX = event.getX(downId); + float multiTouchY = event.getY(downId); + + TouchPoint p = pointMap.get(downId); + + if (p != null) { + p.x = multiTouchX; + p.y = multiTouchY; + } else { + pointMap.put(downId, createPoint(multiTouchX, multiTouchY)); + } + + break; + } + case MotionEvent.ACTION_POINTER_UP: { + + int upId = actionPointer >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + Log.d(TAG, "ACTION_POINTER_UP id : " + upId); + + if (originalIndex == upId) { + Log.d(TAG, "ACTION_POINTER_UP orig up"); + pointMap.remove(upId); + + TouchPoint secondPoint = null; + for (int index = 0, n = pointMap.size(); index < n; index++) { + if (originalIndex != index) { + secondPoint = pointMap.get(index); + if (secondPoint != null) { + secondPoint.setXY(event.getX(index), event.getY(index)); + originalIndex = index; + break; + } + } + } + } + + break; + } + + default: + } + return false; + } + + private TouchPoint createPoint(float x, float y) { + return new TouchPoint(x, y); + } + + class TouchPoint { + float x; + float y; + + TouchPoint(float x, float y) { + this.x = x; + this.y = y; + } + + TouchPoint setXY(float x, float y) { + this.x = x; + this.y = y; + return this; + } + } + +} diff --git a/sample/src/main/java/com/daasuu/sample/widget/GesturePlayerTextureView.java b/sample/src/main/java/com/daasuu/sample/widget/GesturePlayerTextureView.java new file mode 100644 index 0000000..33aacd3 --- /dev/null +++ b/sample/src/main/java/com/daasuu/sample/widget/GesturePlayerTextureView.java @@ -0,0 +1,78 @@ +package com.daasuu.sample.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.MotionEvent; +import android.view.View; + +@SuppressLint("ViewConstructor") +public class GesturePlayerTextureView extends PlayerTextureView implements View.OnTouchListener { + + private final AllGestureDetector allGestureDetector; + + // 基準となる枠のサイズ + private float baseWidthSize = 0; + + public GesturePlayerTextureView(Context context, String path) { + super(context, path); + setOnTouchListener(this); + allGestureDetector = new AllGestureDetector(this); + allGestureDetector.setLimitScaleMin(0.1f); + allGestureDetector.noRotate(); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + allGestureDetector.onTouch(event); + return true; + } + + public void setBaseWidthSize(float baseSize) { + this.baseWidthSize = baseSize; + requestLayout(); + } + + public void updateRotate() { + final int rotation = (int) getRotation(); + + switch (rotation) { + case 0: + super.setRotation(90f); + break; + case 90: + super.setRotation(180f); + break; + case 180: + super.setRotation(270f); + break; + case 270: + super.setRotation(0f); + break; + } + + allGestureDetector.updateAngle(); + } + + + @Override + public void setRotation(float rotation) { + // do nothing + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (videoAspect == DEFAULT_ASPECT || baseWidthSize == 0) return; + + // 正方形 + if (videoAspect == 1.0f) { + setMeasuredDimension((int) baseWidthSize, (int) baseWidthSize); + return; + } + + // 縦長 or 横長 + setMeasuredDimension((int) baseWidthSize, (int) (baseWidthSize / videoAspect)); + + } +} + diff --git a/sample/src/main/java/com/daasuu/sample/widget/PinchGestureDetector.java b/sample/src/main/java/com/daasuu/sample/widget/PinchGestureDetector.java new file mode 100644 index 0000000..d00525f --- /dev/null +++ b/sample/src/main/java/com/daasuu/sample/widget/PinchGestureDetector.java @@ -0,0 +1,112 @@ +package com.daasuu.sample.widget; + +import android.view.MotionEvent; + +class PinchGestureDetector { + private float scale = 1.0f; + + private float adjustDistanceRate = 1f; + + private float distance; + + private float preDistance; + + private PinchGestureListener pinchGestureListener; + + public interface PinchGestureListener { + void onPinchGestureListener(float scale); + } + + PinchGestureDetector(PinchGestureListener dragGestureListener) { + this.pinchGestureListener = dragGestureListener; + } + + public float getScale() { + return this.scale; + } + + public float getDistance() { + return this.distance; + } + + public float getPreDistance() { + return this.preDistance; + } + + synchronized public boolean onTouchEvent(MotionEvent event) { + + float eventX = event.getX() * scale; + float eventY = event.getY() * scale; + int count = event.getPointerCount(); + + int action = event.getAction() & MotionEvent.ACTION_MASK; + int actionPointerIndex = event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK; + + switch (action) { + case MotionEvent.ACTION_DOWN: { + + /** 最初のpointしか来ない */ + + break; + } + case MotionEvent.ACTION_MOVE: { + + if (count == 2) { + + float multiTouchX = event.getX(1) * scale; + float multiTouchY = event.getY(1) * scale; + + distance = culcDistance(eventX, eventY, multiTouchX, multiTouchY); + + float adjustDistance = distance + ((preDistance - distance) * adjustDistanceRate); + + pinchGestureListener.onPinchGestureListener(distance / adjustDistance); + scale *= distance / preDistance; + preDistance = distance; + + } + + break; + } + case MotionEvent.ACTION_POINTER_DOWN: { + + /** 2本の位置を記録 以後、moveにて距離の差分を算出 */ + + if (count == 2) { + int downId = actionPointerIndex >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + + float multiTouchX = event.getX(downId) * scale; + float multiTouchY = event.getY(downId) * scale; + + distance = culcDistance(eventX, eventY, multiTouchX, multiTouchY); + float adjustDistance = distance + ((preDistance - distance) * adjustDistanceRate); + pinchGestureListener.onPinchGestureListener(adjustDistance); + preDistance = distance; + } + + break; + } + case MotionEvent.ACTION_POINTER_UP: { + + distance = 0; + preDistance = 0; + scale = 1.0f; + + break; + } + + default: + } + return false; + } + + private float culcDistance(float x1, float y1, float x2, float y2) { + final float dx = x1 - x2; + final float dy = y1 - y2; + return (float) Math.sqrt(dx * dx + dy * dy); + } + + public void setAdjustDistanceRate(float adjustDistanceRate) { + this.adjustDistanceRate = adjustDistanceRate; + } +} diff --git a/sample/src/main/java/com/daasuu/sample/widget/PlayerTextureView.java b/sample/src/main/java/com/daasuu/sample/widget/PlayerTextureView.java new file mode 100644 index 0000000..092f22d --- /dev/null +++ b/sample/src/main/java/com/daasuu/sample/widget/PlayerTextureView.java @@ -0,0 +1,110 @@ +package com.daasuu.sample.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.net.Uri; +import android.util.Log; +import android.view.Surface; +import android.view.TextureView; + +import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.source.LoopingMediaSource; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoListener; + +@SuppressLint("ViewConstructor") +public class PlayerTextureView extends TextureView implements TextureView.SurfaceTextureListener, VideoListener { + + private final static String TAG = PlayerTextureView.class.getSimpleName(); + + protected static final float DEFAULT_ASPECT = -1f; + private final SimpleExoPlayer player; + protected float videoAspect = DEFAULT_ASPECT; + + public PlayerTextureView(Context context, String path) { + super(context, null, 0); + + // Produces DataSource instances through which media data is loaded. + DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "yourApplicationName")); + + // This is the MediaSource representing the media to be played. + MediaSource videoSource = new ProgressiveMediaSource.Factory(dataSourceFactory) + .createMediaSource(Uri.parse(path)); + + LoopingMediaSource loopingMediaSource = new LoopingMediaSource(videoSource); + + + // SimpleExoPlayer + player = ExoPlayerFactory.newSimpleInstance(context); + // Prepare the player with the source. + player.prepare(loopingMediaSource); + player.addVideoListener(this); + + setSurfaceTextureListener(this); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + if (videoAspect == DEFAULT_ASPECT) return; + + int measuredWidth = getMeasuredWidth(); + int viewHeight = (int) (measuredWidth / videoAspect); + Log.d(TAG, "onMeasure videoAspect = " + videoAspect); + Log.d(TAG, "onMeasure viewWidth = " + measuredWidth + " viewHeight = " + viewHeight); + + setMeasuredDimension(measuredWidth, viewHeight); + } + + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + Log.d(TAG, "onSurfaceTextureAvailable width = " + width + " height = " + height); + + //3. bind the player to the view + player.setVideoSurface(new Surface(surface)); + player.setPlayWhenReady(true); + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + player.stop(); + player.release(); + return false; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + + } + + @Override + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + Log.d(TAG, "width = " + width + " height = " + height + " unappliedRotationDegrees = " + unappliedRotationDegrees + " pixelWidthHeightRatio = " + pixelWidthHeightRatio); + videoAspect = ((float) width / height) * pixelWidthHeightRatio; + Log.d(TAG, "videoAspect = " + videoAspect); + requestLayout(); + } + + @Override + public void onSurfaceSizeChanged(int width, int height) { + + } + + @Override + public void onRenderedFirstFrame() { + + } +} diff --git a/sample/src/main/java/com/daasuu/sample/widget/PortraitView.java b/sample/src/main/java/com/daasuu/sample/widget/PortraitView.java new file mode 100644 index 0000000..e0e46ca --- /dev/null +++ b/sample/src/main/java/com/daasuu/sample/widget/PortraitView.java @@ -0,0 +1,29 @@ +package com.daasuu.sample.widget; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +public class PortraitView extends FrameLayout { + public PortraitView(@NonNull Context context) { + super(context); + } + + public PortraitView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public PortraitView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int height = getMeasuredHeight(); + setMeasuredDimension((int) (height / 16f * 9), height); + } + +} diff --git a/sample/src/main/java/com/daasuu/sample/widget/RotateGestureDetector.java b/sample/src/main/java/com/daasuu/sample/widget/RotateGestureDetector.java new file mode 100644 index 0000000..20d7356 --- /dev/null +++ b/sample/src/main/java/com/daasuu/sample/widget/RotateGestureDetector.java @@ -0,0 +1,120 @@ +package com.daasuu.sample.widget; + +import android.view.MotionEvent; + +class RotateGestureDetector { + + private final static int SLOPE_0 = 10000; + + private RotateGestureListener rotationGestureListener; + + private float angle; + private float downX = 0; + private float downY = 0; + private float downX2 = 0; + private float downY2 = 0; + private boolean isFirstPointerUp = false; + + public interface RotateGestureListener { + void onRotation(float deltaAngle); + } + + RotateGestureDetector(RotateGestureListener rotationGestureListener2) { + this.rotationGestureListener = rotationGestureListener2; + } + + @SuppressWarnings("deprecation") + synchronized public boolean onTouchEvent(MotionEvent event) { + + float eventX = event.getX(); + float eventY = event.getY(); + int count = event.getPointerCount(); + + switch (event.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + downX = eventX; + downY = eventY; + if (count >= 2) { + downX2 = event.getX(1); + downY2 = event.getY(1); + } + break; + case MotionEvent.ACTION_POINTER_DOWN: + downX2 = event.getX(1); + downY2 = event.getY(1); + break; + case MotionEvent.ACTION_MOVE: + + if (count >= 2) { + + // 回転角度の取得 + float angle = getAngle(downX, downY, downX2, downY2, eventX, eventY, event.getX(1), event.getY(1)); + + // 画像の回転 + if (angle != SLOPE_0) { + this.angle -= angle * 180d / Math.PI; + } + + downX2 = event.getX(1); + downY2 = event.getY(1); + + if (rotationGestureListener != null) { + rotationGestureListener.onRotation(getDeltaAngle()); + } + } + + break; + case MotionEvent.ACTION_POINTER_UP: + switch (event.getAction()) { + case MotionEvent.ACTION_POINTER_1_UP: + isFirstPointerUp = true; + break; + default: + } + break; + default: + } + + if (isFirstPointerUp) { + downX = downX2; + downY = downY2; + isFirstPointerUp = false; + } else { + downX = eventX; + downY = eventY; + } + + return true; + } + + private float getDeltaAngle() { + return angle; + } + + private static float getAngle(float xi1, float yi1, float xm1, float ym1, float xi2, float yi2, float xm2, float ym2) { + + // 2本の直線の傾き・y切片を算出 + float firstLinearSlope; + if ((xm1 - xi1) != 0 && (ym1 - yi1) != 0) { + firstLinearSlope = (xm1 - xi1) / (ym1 - yi1); + } else { + return SLOPE_0; + } + + float secondLinearSlope = (xm2 - xi2) / (ym2 - yi2); + if ((xm2 - xi2) != 0 && (ym2 - yi2) != 0) { + secondLinearSlope = (xm2 - xi2) / (ym2 - yi2); + } else { + return SLOPE_0; + } + + if (firstLinearSlope * secondLinearSlope == -1) { + return 90.0f; + } + + float tan = (secondLinearSlope - firstLinearSlope) / (1 + secondLinearSlope * firstLinearSlope); + + return (float) Math.atan(tan); + } + +} diff --git a/sample/src/main/res/drawable/rect_white_drawable.xml b/sample/src/main/res/drawable/rect_white_drawable.xml new file mode 100644 index 0000000..53fb32f --- /dev/null +++ b/sample/src/main/res/drawable/rect_white_drawable.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_basic_usage.xml b/sample/src/main/res/layout/activity_basic_usage.xml new file mode 100644 index 0000000..67b4a08 --- /dev/null +++ b/sample/src/main/res/layout/activity_basic_usage.xml @@ -0,0 +1,97 @@ + + + + + +