内蔵メディアビューアのローディング表示に進捗ゲージを追加
This commit is contained in:
parent
07c8d64b3a
commit
93f40e08a5
@ -56,10 +56,12 @@ import jp.juggler.subwaytooter.api.TootApiTask;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment;
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.ProgressResponseBody;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
import jp.juggler.subwaytooter.view.PinchBitmapView;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class ActMediaViewer extends AppCompatActivity implements View.OnClickListener {
|
||||
|
||||
@ -172,7 +174,7 @@ public class ActMediaViewer extends AppCompatActivity implements View.OnClickLis
|
||||
|
||||
exoPlayer = ExoPlayerFactory.newSimpleInstance( this, new DefaultTrackSelector() );
|
||||
exoPlayer.addListener( player_listener );
|
||||
|
||||
|
||||
exoView.setPlayer( exoPlayer );
|
||||
}
|
||||
|
||||
@ -276,7 +278,7 @@ public class ActMediaViewer extends AppCompatActivity implements View.OnClickLis
|
||||
pbvImage.setVisibility( View.VISIBLE );
|
||||
pbvImage.setBitmap( null );
|
||||
|
||||
new TootApiTask( this, true ) {
|
||||
new TootApiTask( this, TootApiTask.PROGRESS_HORIZONTAL ) {
|
||||
|
||||
private final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
|
||||
@ -325,7 +327,17 @@ public class ActMediaViewer extends AppCompatActivity implements View.OnClickLis
|
||||
|
||||
try{
|
||||
//noinspection ConstantConditions
|
||||
data = response.body().bytes();
|
||||
data = ProgressResponseBody.bytes( response,new ProgressResponseBody.Callback() {
|
||||
@Override public void progressBytes( long bytesRead, long bytesTotal ){
|
||||
// 50MB以上のデータはキャンセルする
|
||||
if( Math.max(bytesRead,bytesTotal) >= 50000000 ){
|
||||
throw new RuntimeException( "media attachment is larger than 50000000" );
|
||||
}
|
||||
publishApiProgressRatio( (int) bytesRead, (int) bytesTotal );
|
||||
}
|
||||
} );
|
||||
|
||||
|
||||
return new TootApiResult( "" );
|
||||
}catch( Throwable ex ){
|
||||
return new TootApiResult( Utils.formatError( ex, "content error." ) );
|
||||
@ -388,15 +400,17 @@ public class ActMediaViewer extends AppCompatActivity implements View.OnClickLis
|
||||
}
|
||||
}
|
||||
|
||||
static class DownloadHistory{
|
||||
static class DownloadHistory {
|
||||
final long time;
|
||||
@NonNull final String url;
|
||||
DownloadHistory(long time,@NonNull String url){
|
||||
|
||||
DownloadHistory( long time, @NonNull String url ){
|
||||
this.time = time;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
static final LinkedList<DownloadHistory> download_history_list = new LinkedList<>( );
|
||||
|
||||
static final LinkedList< DownloadHistory > download_history_list = new LinkedList<>();
|
||||
static final long DOWNLOAD_REPEAT_EXPIRE = 3000L;
|
||||
|
||||
void download( @NonNull TootAttachment ta ){
|
||||
@ -412,7 +426,7 @@ public class ActMediaViewer extends AppCompatActivity implements View.OnClickLis
|
||||
Utils.showToast( this, false, "download manager is not on your device." );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String url = ta.getLargeUrl( App1.pref );
|
||||
if( url == null ) return;
|
||||
|
||||
@ -437,24 +451,24 @@ public class ActMediaViewer extends AppCompatActivity implements View.OnClickLis
|
||||
download_history_list.addLast( new DownloadHistory( now, url ) );
|
||||
}
|
||||
|
||||
String fileName =null;
|
||||
|
||||
String fileName = null;
|
||||
|
||||
try{
|
||||
List<String> pathSegments = Uri.parse( url ).getPathSegments();
|
||||
List< String > pathSegments = Uri.parse( url ).getPathSegments();
|
||||
if( pathSegments != null ){
|
||||
int size = pathSegments.size();
|
||||
for( int i= size-1;i>=0;--i){
|
||||
String s = pathSegments.get(i);
|
||||
if( !TextUtils.isEmpty( s )){
|
||||
for( int i = size - 1 ; i >= 0 ; -- i ){
|
||||
String s = pathSegments.get( i );
|
||||
if( ! TextUtils.isEmpty( s ) ){
|
||||
fileName = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch(Throwable ex){
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}
|
||||
|
||||
|
||||
if( fileName == null ){
|
||||
fileName = url
|
||||
.replaceFirst( "https?://", "" )
|
||||
|
@ -20,6 +20,7 @@ import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
|
||||
import com.bumptech.glide.load.model.GlideUrl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
@ -46,12 +47,14 @@ import jp.juggler.subwaytooter.table.UserRelation;
|
||||
import jp.juggler.subwaytooter.util.CustomEmojiCache;
|
||||
import jp.juggler.subwaytooter.util.CustomEmojiLister;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.ProgressResponseBody;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
import okhttp3.Cache;
|
||||
import okhttp3.CacheControl;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.CipherSuite;
|
||||
import okhttp3.ConnectionSpec;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Response;
|
||||
import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
|
||||
@ -107,8 +110,7 @@ public class App1 extends Application {
|
||||
super( context, DB_NAME, null, DB_VERSION );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate( SQLiteDatabase db ){
|
||||
@Override public void onCreate( SQLiteDatabase db ){
|
||||
LogData.onDBCreate( db );
|
||||
//
|
||||
SavedAccount.onDBCreate( db );
|
||||
@ -125,8 +127,7 @@ public class App1 extends Application {
|
||||
TagSet.onDBCreate( db );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade( SQLiteDatabase db, int oldVersion, int newVersion ){
|
||||
@Override public void onUpgrade( SQLiteDatabase db, int oldVersion, int newVersion ){
|
||||
LogData.onDBUpgrade( db, oldVersion, newVersion );
|
||||
//
|
||||
SavedAccount.onDBUpgrade( db, oldVersion, newVersion );
|
||||
@ -217,7 +218,8 @@ public class App1 extends Application {
|
||||
.readTimeout( 60, TimeUnit.SECONDS )
|
||||
.writeTimeout( 60, TimeUnit.SECONDS )
|
||||
.pingInterval( 10, TimeUnit.SECONDS )
|
||||
.connectionSpecs( spec_list );
|
||||
.connectionSpecs( spec_list )
|
||||
.addInterceptor( ProgressResponseBody.makeInterceptor() );
|
||||
|
||||
return builder;
|
||||
}
|
||||
@ -237,7 +239,7 @@ public class App1 extends Application {
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public static CustomEmojiCache custom_emoji_cache;
|
||||
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public static CustomEmojiLister custom_emoji_lister;
|
||||
|
||||
@ -400,15 +402,16 @@ public class App1 extends Application {
|
||||
}
|
||||
|
||||
public static void setActivityTheme( @NonNull Activity activity, boolean bNoActionBar ){
|
||||
setActivityTheme( activity, bNoActionBar ,false);
|
||||
setActivityTheme( activity, bNoActionBar, false );
|
||||
}
|
||||
public static void setActivityTheme( @NonNull Activity activity, boolean bNoActionBar ,boolean forceDark){
|
||||
|
||||
public static void setActivityTheme( @NonNull Activity activity, boolean bNoActionBar, boolean forceDark ){
|
||||
|
||||
prepare( activity.getApplicationContext() );
|
||||
|
||||
int theme_idx = pref.getInt( Pref.KEY_UI_THEME, 0 );
|
||||
|
||||
if( forceDark ) theme_idx =1;
|
||||
if( forceDark ) theme_idx = 1;
|
||||
|
||||
switch( theme_idx ){
|
||||
|
||||
@ -490,16 +493,14 @@ public class App1 extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Activity開始時に設定を読み直す
|
||||
public static boolean disable_emoji_animation;
|
||||
public static boolean allow_non_space_before_emoji_shortcode;
|
||||
private static void reloadConfig(){
|
||||
disable_emoji_animation = pref.getBoolean( Pref.KEY_DISABLE_EMOJI_ANIMATION,false );
|
||||
allow_non_space_before_emoji_shortcode = pref.getBoolean( Pref.KEY_ALLOW_NON_SPACE_BEFORE_EMOJI_SHORTCODE,false );
|
||||
}
|
||||
|
||||
private static void reloadConfig(){
|
||||
disable_emoji_animation = pref.getBoolean( Pref.KEY_DISABLE_EMOJI_ANIMATION, false );
|
||||
allow_non_space_before_emoji_shortcode = pref.getBoolean( Pref.KEY_ALLOW_NON_SPACE_BEFORE_EMOJI_SHORTCODE, false );
|
||||
}
|
||||
|
||||
// Chrome Custom Tab を開く
|
||||
public static void openCustomTab( @NonNull Activity activity, @NonNull String url ){
|
||||
@ -523,13 +524,13 @@ public class App1 extends Application {
|
||||
builder.setToolbarColor( Styler.getAttributeColor( activity, R.attr.colorPrimary ) ).setShowTitle( true );
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
customTabsIntent.launchUrl( activity, Uri.parse( url ) );
|
||||
}catch(Throwable ex){
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "openCustomTab: failed." );
|
||||
}
|
||||
}
|
||||
|
||||
public static void openCustomTab( @NonNull Activity activity, @NonNull TootAttachment ta){
|
||||
String url = ta.getLargeUrl(pref);
|
||||
public static void openCustomTab( @NonNull Activity activity, @NonNull TootAttachment ta ){
|
||||
String url = ta.getLargeUrl( pref );
|
||||
if( url != null ){
|
||||
openCustomTab( activity, url );
|
||||
}
|
||||
|
@ -22,21 +22,43 @@ public abstract class TootApiTask extends AsyncTask< Void, Void, TootApiResult >
|
||||
|
||||
@NonNull protected final TootApiClient client;
|
||||
|
||||
protected TootApiTask( Activity _activity, SavedAccount access_info, boolean bShowProgress ){
|
||||
@SuppressWarnings("WeakerAccess") public static final int PROGRESS_NONE = - 1;
|
||||
@SuppressWarnings("WeakerAccess") public static final int PROGRESS_SPINNER = ProgressDialog.STYLE_SPINNER;
|
||||
@SuppressWarnings("WeakerAccess") public static final int PROGRESS_HORIZONTAL = ProgressDialog.STYLE_HORIZONTAL;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public TootApiTask( Activity _activity, SavedAccount access_info, boolean bShowProgress ){
|
||||
this( _activity, access_info, bShowProgress ? PROGRESS_SPINNER : PROGRESS_NONE );
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public TootApiTask( Activity _activity, SavedAccount access_info, int progress_style ){
|
||||
this.client = new TootApiClient( _activity, this );
|
||||
client.setAccount( access_info );
|
||||
if( bShowProgress ) showProgress( _activity );
|
||||
showProgress( _activity, progress_style );
|
||||
}
|
||||
|
||||
protected TootApiTask( Activity _activity, String instance, boolean bShowProgress ){
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public TootApiTask( Activity _activity, String instance, boolean bShowProgress ){
|
||||
this( _activity, instance, bShowProgress ? PROGRESS_SPINNER : PROGRESS_NONE );
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public TootApiTask( Activity _activity, String instance, int progress_style ){
|
||||
this.client = new TootApiClient( _activity, this );
|
||||
client.setInstance( instance );
|
||||
if( bShowProgress ) showProgress( _activity );
|
||||
showProgress( _activity, progress_style );
|
||||
}
|
||||
|
||||
protected TootApiTask( Activity _activity, boolean bShowProgress ){
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public TootApiTask( Activity _activity, boolean bShowProgress ){
|
||||
this( _activity, bShowProgress ? PROGRESS_SPINNER : PROGRESS_NONE );
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public TootApiTask( Activity _activity, int progress_style ){
|
||||
this.client = new TootApiClient( _activity, this );
|
||||
if( bShowProgress ) showProgress( _activity );
|
||||
showProgress( _activity, progress_style );
|
||||
}
|
||||
|
||||
public TootApiTask setProgressPrefix( String s ){
|
||||
@ -54,6 +76,7 @@ public abstract class TootApiTask extends AsyncTask< Void, Void, TootApiResult >
|
||||
if( progress != null ){
|
||||
Utils.runOnMainThread( new Runnable() {
|
||||
@Override public void run(){
|
||||
progress.setIndeterminate( true );
|
||||
if( ! TextUtils.isEmpty( progress_prefix ) ){
|
||||
progress.setMessage( progress_prefix + "\n" + s );
|
||||
}else{
|
||||
@ -64,6 +87,18 @@ public abstract class TootApiTask extends AsyncTask< Void, Void, TootApiResult >
|
||||
}
|
||||
}
|
||||
|
||||
public void publishApiProgressRatio( final int value, final int max ){
|
||||
if( progress != null ){
|
||||
Utils.runOnMainThread( new Runnable() {
|
||||
@Override public void run(){
|
||||
progress.setIndeterminate( false );
|
||||
progress.setProgress( value );
|
||||
progress.setMax( max );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
@Override protected abstract TootApiResult doInBackground( Void... voids );
|
||||
@ -84,13 +119,21 @@ public abstract class TootApiTask extends AsyncTask< Void, Void, TootApiResult >
|
||||
private ProgressDialog progress;
|
||||
private String progress_prefix;
|
||||
|
||||
private void showProgress( Activity activity ){
|
||||
private void showProgress( Activity activity, int progressStyle ){
|
||||
|
||||
if( progressStyle == PROGRESS_NONE ) return;
|
||||
|
||||
//noinspection deprecation
|
||||
this.progress = new ProgressDialog( activity );
|
||||
progress.setIndeterminate( true );
|
||||
progress.setCancelable( true );
|
||||
progress.setProgressStyle( progressStyle );
|
||||
progress.setIndeterminate( true );
|
||||
progress.setMax( 1 );
|
||||
if( ! TextUtils.isEmpty( progress_prefix ) ){
|
||||
progress.setMessage( progress_prefix );
|
||||
}else{
|
||||
// HORIZONTALスタイルの場合、初期メッセージがないと後からメッセージを指定しても表示されない
|
||||
progress.setMessage( " " );
|
||||
}
|
||||
progress.setOnCancelListener( new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
|
@ -0,0 +1,365 @@
|
||||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
import okio.ByteString;
|
||||
import okio.Options;
|
||||
import okio.Sink;
|
||||
import okio.Source;
|
||||
import okio.Timeout;
|
||||
|
||||
public class ProgressResponseBody extends ResponseBody {
|
||||
|
||||
static final LogCategory log = new LogCategory( "ProgressResponseBody" );
|
||||
|
||||
@NonNull private final ResponseBody originalBody;
|
||||
|
||||
// please append this for OkHttpClient.Builder#addInterceptor().
|
||||
// ex) builder.addInterceptor( ProgressResponseBody.makeInterceptor() );
|
||||
public static Interceptor makeInterceptor(){
|
||||
return new Interceptor() {
|
||||
@Override public Response intercept( @NonNull Chain chain ) throws IOException{
|
||||
|
||||
Response originalResponse = chain.proceed( chain.request() );
|
||||
if( originalResponse == null )
|
||||
throw new RuntimeException( "makeInterceptor: chain.proceed() returns null." );
|
||||
|
||||
ResponseBody originalBody = originalResponse.body();
|
||||
if( originalBody == null )
|
||||
throw new RuntimeException( "makeInterceptor: originalResponse.body() reruens null." );
|
||||
|
||||
return originalResponse.newBuilder()
|
||||
.body( new ProgressResponseBody( originalBody ) )
|
||||
.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
then you can read response body's bytes() with progress callback.
|
||||
example:
|
||||
byte[] data = ProgressResponseBody.bytes( response, new ProgressResponseBody.Callback() {
|
||||
@Override public void progressBytes( long bytesRead, long bytesTotal ){
|
||||
publishApiProgressRatio( (int) bytesRead, (int) bytesTotal );
|
||||
}
|
||||
} );
|
||||
*/
|
||||
|
||||
public interface Callback {
|
||||
void progressBytes(
|
||||
long bytesRead
|
||||
, long bytesTotal // initial value is Content-Length, but it may only hint, it may glows greater.
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static byte[] bytes( @NonNull Response response, @Nullable Callback callback ) throws IOException{
|
||||
ResponseBody body = response.body();
|
||||
if( body == null ) throw new RuntimeException( "response.body() is null." );
|
||||
return bytes( body, callback );
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static byte[] bytes( @NonNull ResponseBody body, @Nullable Callback callback ) throws IOException{
|
||||
if( body instanceof ProgressResponseBody ){
|
||||
( (ProgressResponseBody) body ).callback = callback;
|
||||
}
|
||||
return body.bytes();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// internal
|
||||
|
||||
private ProgressResponseBody( @NonNull ResponseBody originalBody ){
|
||||
this.originalBody = originalBody;
|
||||
}
|
||||
|
||||
@Nullable private Callback callback;
|
||||
|
||||
@Override public MediaType contentType(){
|
||||
return originalBody.contentType();
|
||||
}
|
||||
|
||||
@Override public long contentLength(){
|
||||
return originalBody.contentLength();
|
||||
}
|
||||
|
||||
/*
|
||||
RequestBody.bytes() is defined as final, We can't override it.
|
||||
Make WrappedBufferedSource to capture BufferedSource.readByteArray().
|
||||
*/
|
||||
|
||||
private BufferedSource wrappedSource;
|
||||
|
||||
@Override public BufferedSource source(){
|
||||
if( wrappedSource == null ){
|
||||
|
||||
BufferedSource originalSource = originalBody.source();
|
||||
if( originalSource == null ) return null;
|
||||
|
||||
try{
|
||||
// if it is RealBufferedSource, I can access to source public field via reflection.
|
||||
final Field field_source = originalSource.getClass().getField( "source" );
|
||||
|
||||
// If there is the method, create the wrapper.
|
||||
wrappedSource = new ForwardingBufferedSource( originalSource ) {
|
||||
@Override public byte[] readByteArray() throws IOException{
|
||||
/*
|
||||
RealBufferedSource.readByteArray() does:
|
||||
- buffer.writeAll(source);
|
||||
- return buffer.readByteArray(buffer.size());
|
||||
|
||||
We do same things using Reflection, with progress.
|
||||
*/
|
||||
|
||||
try{
|
||||
long contentLength = originalBody.contentLength();
|
||||
Buffer buffer = originalSource.buffer();
|
||||
Source source = (Source) field_source.get( originalSource );
|
||||
|
||||
if( source == null )
|
||||
throw new IllegalArgumentException( "source == null" );
|
||||
|
||||
// same thing of Buffer.writeAll(), with counting.
|
||||
long nRead = 0;
|
||||
if( callback != null ){
|
||||
callback.progressBytes( 0, Math.max( contentLength, 1 ) );
|
||||
}
|
||||
for( ; ; ){
|
||||
long delta = source.read( buffer, 8192 );
|
||||
if( delta == - 1L ) break;
|
||||
nRead += delta;
|
||||
if( nRead > 0 && callback != null ){
|
||||
callback.progressBytes( nRead, Math.max( contentLength, nRead ) );
|
||||
}
|
||||
}
|
||||
// EOS時の進捗
|
||||
callback.progressBytes( nRead, Math.max( contentLength, nRead ) );
|
||||
|
||||
return buffer.readByteArray();
|
||||
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
log.e( "readByteArray() failed. " );
|
||||
return originalSource.readByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
}catch( Throwable ex ){
|
||||
log.e( "can't access to RealBufferedSource#source field." );
|
||||
wrappedSource = originalSource;
|
||||
}
|
||||
}
|
||||
return wrappedSource;
|
||||
}
|
||||
|
||||
// To avoid double buffering, We have to make ForwardingBufferedSource.
|
||||
static class ForwardingBufferedSource implements BufferedSource {
|
||||
@NonNull final BufferedSource originalSource;
|
||||
|
||||
ForwardingBufferedSource( @NonNull BufferedSource originalSource ){
|
||||
this.originalSource = originalSource;
|
||||
}
|
||||
|
||||
@Override public Buffer buffer(){
|
||||
return originalSource.buffer();
|
||||
}
|
||||
|
||||
@Override public boolean exhausted() throws IOException{
|
||||
return originalSource.exhausted();
|
||||
}
|
||||
|
||||
@Override public void require( long byteCount ) throws IOException{
|
||||
originalSource.require( byteCount );
|
||||
}
|
||||
|
||||
@Override public boolean request( long byteCount ) throws IOException{
|
||||
return originalSource.request( byteCount );
|
||||
}
|
||||
|
||||
@Override public byte readByte() throws IOException{
|
||||
return originalSource.readByte();
|
||||
}
|
||||
|
||||
@Override public short readShort() throws IOException{
|
||||
return originalSource.readShort();
|
||||
}
|
||||
|
||||
@Override public short readShortLe() throws IOException{
|
||||
return originalSource.readShortLe();
|
||||
}
|
||||
|
||||
@Override public int readInt() throws IOException{
|
||||
return originalSource.readInt();
|
||||
}
|
||||
|
||||
@Override public int readIntLe() throws IOException{
|
||||
return originalSource.readIntLe();
|
||||
}
|
||||
|
||||
@Override public long readLong() throws IOException{
|
||||
return originalSource.readLong();
|
||||
}
|
||||
|
||||
@Override public long readLongLe() throws IOException{
|
||||
return originalSource.readLongLe();
|
||||
}
|
||||
|
||||
@Override public long readDecimalLong() throws IOException{
|
||||
return originalSource.readDecimalLong();
|
||||
}
|
||||
|
||||
@Override public long readHexadecimalUnsignedLong() throws IOException{
|
||||
return originalSource.readHexadecimalUnsignedLong();
|
||||
}
|
||||
|
||||
@Override public void skip( long byteCount ) throws IOException{
|
||||
originalSource.skip( byteCount );
|
||||
}
|
||||
|
||||
@Override public ByteString readByteString() throws IOException{
|
||||
return originalSource.readByteString();
|
||||
}
|
||||
|
||||
@Override public ByteString readByteString( long byteCount ) throws IOException{
|
||||
return originalSource.readByteString( byteCount );
|
||||
}
|
||||
|
||||
@Override public int select( @NonNull Options options ) throws IOException{
|
||||
return originalSource.select( options );
|
||||
}
|
||||
|
||||
@Override public byte[] readByteArray() throws IOException{
|
||||
return originalSource.readByteArray();
|
||||
}
|
||||
|
||||
@Override public byte[] readByteArray( long byteCount ) throws IOException{
|
||||
return originalSource.readByteArray( byteCount );
|
||||
}
|
||||
|
||||
@Override public int read( @NonNull byte[] sink ) throws IOException{
|
||||
return originalSource.read( sink );
|
||||
}
|
||||
|
||||
@Override public void readFully( @NonNull byte[] sink ) throws IOException{
|
||||
originalSource.readFully( sink );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read( @NonNull byte[] sink, int offset, int byteCount ) throws IOException{
|
||||
return originalSource.read( sink, offset, byteCount );
|
||||
}
|
||||
|
||||
@Override public void readFully( @NonNull Buffer sink, long byteCount ) throws IOException{
|
||||
originalSource.readFully( sink, byteCount );
|
||||
}
|
||||
|
||||
@Override public long readAll( @NonNull Sink sink ) throws IOException{
|
||||
return originalSource.readAll( sink );
|
||||
}
|
||||
|
||||
@Override public String readUtf8() throws IOException{
|
||||
return originalSource.readUtf8();
|
||||
}
|
||||
|
||||
@Override public String readUtf8( long byteCount ) throws IOException{
|
||||
return originalSource.readUtf8( byteCount );
|
||||
}
|
||||
|
||||
@Nullable @Override public String readUtf8Line() throws IOException{
|
||||
return originalSource.readUtf8Line();
|
||||
}
|
||||
|
||||
@Override public String readUtf8LineStrict() throws IOException{
|
||||
return originalSource.readUtf8LineStrict();
|
||||
}
|
||||
|
||||
@Override public String readUtf8LineStrict( long limit ) throws IOException{
|
||||
return originalSource.readUtf8LineStrict( limit );
|
||||
}
|
||||
|
||||
@Override public int readUtf8CodePoint() throws IOException{
|
||||
return originalSource.readUtf8CodePoint();
|
||||
}
|
||||
|
||||
@Override public String readString( @NonNull Charset charset ) throws IOException{
|
||||
return originalSource.readString( charset );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readString( long byteCount, @NonNull Charset charset ) throws IOException{
|
||||
return originalSource.readString( byteCount, charset );
|
||||
}
|
||||
|
||||
@Override public long indexOf( byte b ) throws IOException{
|
||||
return originalSource.indexOf( b );
|
||||
}
|
||||
|
||||
@Override public long indexOf( byte b, long fromIndex ) throws IOException{
|
||||
return originalSource.indexOf( b, fromIndex );
|
||||
}
|
||||
|
||||
@Override public long indexOf( byte b, long fromIndex, long toIndex ) throws IOException{
|
||||
return originalSource.indexOf( b, fromIndex, toIndex );
|
||||
}
|
||||
|
||||
@Override public long indexOf( @NonNull ByteString bytes ) throws IOException{
|
||||
return originalSource.indexOf( bytes );
|
||||
}
|
||||
|
||||
@Override
|
||||
public long indexOf( @NonNull ByteString bytes, long fromIndex ) throws IOException{
|
||||
return originalSource.indexOf( bytes, fromIndex );
|
||||
}
|
||||
|
||||
@Override public long indexOfElement( @NonNull ByteString targetBytes ) throws IOException{
|
||||
return originalSource.indexOfElement( targetBytes );
|
||||
}
|
||||
|
||||
@Override
|
||||
public long indexOfElement( @NonNull ByteString targetBytes, long fromIndex ) throws IOException{
|
||||
return originalSource.indexOfElement( targetBytes, fromIndex );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rangeEquals( long offset, @NonNull ByteString bytes ) throws IOException{
|
||||
return originalSource.rangeEquals( offset, bytes );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rangeEquals( long offset, @NonNull ByteString bytes, int bytesOffset, int byteCount ) throws IOException{
|
||||
return originalSource.rangeEquals( offset, bytes, bytesOffset, byteCount );
|
||||
}
|
||||
|
||||
@Override public InputStream inputStream(){
|
||||
return originalSource.inputStream();
|
||||
}
|
||||
|
||||
@Override public long read( @NonNull Buffer sink, long byteCount ) throws IOException{
|
||||
return originalSource.read( sink, byteCount );
|
||||
}
|
||||
|
||||
@Override public Timeout timeout(){
|
||||
return originalSource.timeout();
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException{
|
||||
originalSource.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -92,6 +92,11 @@ public class PinchBitmapView extends View {
|
||||
|
||||
@Override protected void onSizeChanged( int w, int h, int oldw, int oldh ){
|
||||
super.onSizeChanged( w, h, oldw, oldh );
|
||||
|
||||
view_w = Math.max( 1f, w );
|
||||
view_h = Math.max( 1f, h );
|
||||
view_aspect = view_w / view_h;
|
||||
|
||||
initializeScale();
|
||||
}
|
||||
|
||||
@ -101,19 +106,15 @@ public class PinchBitmapView extends View {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ビットマップを変更した時とビューのサイズが変わった時と画像をクリックした時に呼ばれる
|
||||
// 表示位置を再計算して再描画
|
||||
// 表示位置の初期化
|
||||
// 呼ばれるのは、ビットマップを変更した時、ビューのサイズが変わった時、画像をクリックした時
|
||||
void initializeScale(){
|
||||
if( bitmap != null && ! bitmap.isRecycled() ){
|
||||
if( bitmap != null && ! bitmap.isRecycled() && view_w >= 1f ){
|
||||
|
||||
bitmap_w = Math.max( 1f, bitmap.getWidth() );
|
||||
bitmap_h = Math.max( 1f, bitmap.getHeight() );
|
||||
bitmap_aspect = bitmap_w / bitmap_h;
|
||||
|
||||
view_w = Math.max( 1f, this.getWidth() );
|
||||
view_h = Math.max( 1f, this.getHeight() );
|
||||
view_aspect = view_w / view_h;
|
||||
|
||||
if( view_aspect > bitmap_aspect ){
|
||||
current_scale = view_h / bitmap_h;
|
||||
}else{
|
||||
@ -127,6 +128,7 @@ public class PinchBitmapView extends View {
|
||||
current_trans_y = ( view_h - draw_h ) / 2f;
|
||||
}
|
||||
|
||||
// 画像がnullに変化した時も再描画が必要
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@ -148,17 +150,20 @@ public class PinchBitmapView extends View {
|
||||
|
||||
@Override public boolean onTouchEvent( MotionEvent ev ){
|
||||
|
||||
if( bitmap == null || bitmap.isRecycled() ) return false;
|
||||
if( bitmap == null
|
||||
|| bitmap.isRecycled()
|
||||
|| view_w < 1f
|
||||
) return false;
|
||||
|
||||
int action = ev.getAction();
|
||||
|
||||
if( action == MotionEvent.ACTION_DOWN ){
|
||||
if( velocityTracker != null ){
|
||||
velocityTracker.recycle();
|
||||
velocityTracker = null;
|
||||
velocityTracker.clear();
|
||||
}else{
|
||||
velocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
|
||||
velocityTracker = VelocityTracker.obtain();
|
||||
velocityTracker.addMovement( ev );
|
||||
|
||||
bDrag = bPointerCountChanged = false;
|
||||
@ -174,8 +179,7 @@ public class PinchBitmapView extends View {
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
// タッチ操作中に指の数を変えた
|
||||
bDrag = true;
|
||||
bPointerCountChanged = true;
|
||||
bDrag = bPointerCountChanged = true;
|
||||
startTracking( ev );
|
||||
break;
|
||||
|
||||
@ -186,53 +190,7 @@ public class PinchBitmapView extends View {
|
||||
case MotionEvent.ACTION_UP:
|
||||
nextTracking( ev );
|
||||
|
||||
if( ! bDrag ){
|
||||
// 指を動かしていないならクリック操作だったのだろう
|
||||
|
||||
performClick();
|
||||
|
||||
}else if( ! bPointerCountChanged ){
|
||||
// 指の数を変えていないならページめくり操作かもしれない
|
||||
|
||||
velocityTracker.computeCurrentVelocity( 1000 );
|
||||
final float xv = velocityTracker.getXVelocity();
|
||||
float yv = velocityTracker.getYVelocity();
|
||||
|
||||
float image_move_x = Math.abs( current_trans_x - start_image_trans_x );
|
||||
float image_move_y = Math.abs( current_trans_y - start_image_trans_y );
|
||||
|
||||
float draw_w = bitmap_w * current_scale;
|
||||
|
||||
if( Math.abs( xv ) < Math.abs( yv ) / 8 ){
|
||||
// 指を動かした方向の角度が左右ではなかった
|
||||
log.d( "flick is vertical." );
|
||||
|
||||
}else if( Math.abs( xv ) < ( draw_w <= view_w ? swipe_velocity2 : swipe_velocity ) ){
|
||||
// 左右方向の強さが足りなかった
|
||||
log.d( "velocity %f not enough to paging", xv );
|
||||
|
||||
}else if( image_move_x >= drag_width
|
||||
|| image_move_y >= drag_width * 5f
|
||||
){
|
||||
// 「画像を動かした」かどうかの最終チェック
|
||||
log.d( "image was moved. not paging action. %f %f "
|
||||
, image_move_x / drag_width
|
||||
, image_move_y / drag_width
|
||||
);
|
||||
}else{
|
||||
log.d( "paging! %f %f %f"
|
||||
, image_move_x / drag_width
|
||||
, image_move_y / drag_width
|
||||
,xv
|
||||
);
|
||||
|
||||
Utils.runOnMainThread( new Runnable() {
|
||||
@Override public void run(){
|
||||
if( callback != null ) callback.onSwipe( xv >= 0f ? - 1 : 1 );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
checkClickOrPaging();
|
||||
|
||||
if( velocityTracker != null ){
|
||||
velocityTracker.recycle();
|
||||
@ -244,6 +202,57 @@ public class PinchBitmapView extends View {
|
||||
return true;
|
||||
}
|
||||
|
||||
void checkClickOrPaging(){
|
||||
if( ! bDrag ){
|
||||
|
||||
// 指を動かしていないならクリック操作だったのだろう
|
||||
performClick();
|
||||
|
||||
}else if( ! bPointerCountChanged && velocityTracker != null ){
|
||||
|
||||
// 指の数を変えていないならページめくり操作かもしれない
|
||||
|
||||
velocityTracker.computeCurrentVelocity( 1000 );
|
||||
final float xv = velocityTracker.getXVelocity();
|
||||
final float yv = velocityTracker.getYVelocity();
|
||||
|
||||
float image_move_x = Math.abs( current_trans_x - start_image_trans_x );
|
||||
float image_move_y = Math.abs( current_trans_y - start_image_trans_y );
|
||||
|
||||
float draw_w = bitmap_w * current_scale;
|
||||
|
||||
if( Math.abs( xv ) < Math.abs( yv ) / 8 ){
|
||||
// 指を動かした方向の角度が左右ではなかった
|
||||
log.d( "flick is vertical." );
|
||||
|
||||
}else if( Math.abs( xv ) < ( draw_w <= view_w ? swipe_velocity2 : swipe_velocity ) ){
|
||||
// 左右方向の強さが足りなかった
|
||||
log.d( "velocity %f not enough to paging", xv );
|
||||
|
||||
}else if( image_move_x >= drag_width
|
||||
|| image_move_y >= drag_width * 5f
|
||||
){
|
||||
// 「画像を動かした」かどうかの最終チェック
|
||||
log.d( "image was moved. not paging action. %f %f "
|
||||
, image_move_x / drag_width
|
||||
, image_move_y / drag_width
|
||||
);
|
||||
}else{
|
||||
log.d( "paging! %f %f %f"
|
||||
, image_move_x / drag_width
|
||||
, image_move_y / drag_width
|
||||
, xv
|
||||
);
|
||||
|
||||
Utils.runOnMainThread( new Runnable() {
|
||||
@Override public void run(){
|
||||
if( callback != null ) callback.onSwipe( xv >= 0f ? - 1 : 1 );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// マルチタッチの中心位置の計算
|
||||
static class PointerAvg {
|
||||
|
||||
@ -305,16 +314,15 @@ public class PinchBitmapView extends View {
|
||||
float view_aspect;
|
||||
|
||||
void startTracking( MotionEvent ev ){
|
||||
|
||||
// 追跡開始時の指の位置
|
||||
start_pos.update( ev );
|
||||
pos.update( ev );
|
||||
|
||||
// 追跡開始時の画像の位置
|
||||
start_image_trans_x = current_trans_x;
|
||||
start_image_trans_y = current_trans_y;
|
||||
start_image_scale = current_scale;
|
||||
|
||||
view_w = Math.max( 1f, this.getWidth() );
|
||||
view_h = Math.max( 1f, this.getHeight() );
|
||||
view_aspect = view_w / view_h;
|
||||
|
||||
if( view_aspect > bitmap_aspect ){
|
||||
scale_min = view_h / bitmap_h / 2f;
|
||||
scale_max = view_w / bitmap_w * 8f;
|
||||
@ -325,13 +333,13 @@ public class PinchBitmapView extends View {
|
||||
if( scale_max < scale_min ) scale_max = scale_min * 16f;
|
||||
}
|
||||
|
||||
final Matrix tracking_matrix = new Matrix();
|
||||
final Matrix tracking_matrix_inv = new Matrix();
|
||||
final float[] avg_on_image1 = new float[ 2 ];
|
||||
final float[] avg_on_image2 = new float[ 2 ];
|
||||
private final Matrix tracking_matrix = new Matrix();
|
||||
private final Matrix tracking_matrix_inv = new Matrix();
|
||||
private final float[] avg_on_image1 = new float[ 2 ];
|
||||
private final float[] avg_on_image2 = new float[ 2 ];
|
||||
|
||||
// 画面上の指の位置から画像中の指の位置を調べる
|
||||
void getCoordinateOnImage( @NonNull float[] dst, @NonNull float[] src ){
|
||||
private void getCoordinateOnImage( @NonNull float[] dst, @NonNull float[] src ){
|
||||
tracking_matrix.reset();
|
||||
tracking_matrix.postScale( current_scale, current_scale );
|
||||
tracking_matrix.postTranslate( current_trans_x, current_trans_y );
|
||||
@ -357,9 +365,7 @@ public class PinchBitmapView extends View {
|
||||
getCoordinateOnImage( avg_on_image1, pos.avg );
|
||||
|
||||
// ズーム率を変更する
|
||||
float new_scale = start_image_scale * pos.max_radius / start_pos.max_radius;
|
||||
new_scale = new_scale < scale_min ? scale_min : new_scale > scale_max ? scale_max : new_scale;
|
||||
current_scale = new_scale;
|
||||
current_scale = clip( scale_min, scale_max, start_image_scale * pos.max_radius / start_pos.max_radius );
|
||||
|
||||
// 再び調べる
|
||||
getCoordinateOnImage( avg_on_image2, pos.avg );
|
||||
@ -368,7 +374,6 @@ public class PinchBitmapView extends View {
|
||||
start_image_trans_x += current_scale * ( avg_on_image2[ 0 ] - avg_on_image1[ 0 ] );
|
||||
start_image_trans_y += current_scale * ( avg_on_image2[ 1 ] - avg_on_image1[ 1 ] );
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// 平行移動
|
||||
@ -384,33 +389,32 @@ public class PinchBitmapView extends View {
|
||||
bDrag = true;
|
||||
}
|
||||
|
||||
// 画像の移動量
|
||||
float trans_x = start_image_trans_x + move_x;
|
||||
float trans_y = start_image_trans_y + move_y;
|
||||
|
||||
// 画像サイズとビューサイズを使って移動可能範囲をクリッピング
|
||||
float draw_w = bitmap_w * current_scale;
|
||||
float draw_h = bitmap_h * current_scale;
|
||||
if( draw_w <= view_w ){
|
||||
float remain = view_w - draw_w;
|
||||
trans_x = remain / 2f;
|
||||
}else{
|
||||
float remain = draw_w - view_w;
|
||||
trans_x = trans_x >= 0f ? 0f : trans_x < - remain ? - remain : trans_x;
|
||||
}
|
||||
if( draw_h <= view_h ){
|
||||
float remain = view_h - draw_h;
|
||||
trans_y = remain / 2f;
|
||||
}else{
|
||||
float remain = draw_h - view_h;
|
||||
trans_y = trans_y >= 0f ? 0f : trans_y < - remain ? - remain : trans_y;
|
||||
}
|
||||
|
||||
// 画像の表示位置を変更して再描画
|
||||
current_trans_x = trans_x;
|
||||
current_trans_y = trans_y;
|
||||
invalidate();
|
||||
// 画像の表示位置を更新
|
||||
current_trans_x = clipTranslate( view_w, bitmap_w, current_scale, start_image_trans_x + move_x );
|
||||
current_trans_y = clipTranslate( view_h, bitmap_h, current_scale, start_image_trans_y + move_y );
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// 数値を範囲内にクリップする
|
||||
private static float clip( float min, float max, float v ){
|
||||
return v < min ? min : v > max ? max : v;
|
||||
}
|
||||
|
||||
// ビューの幅と画像の描画サイズを元に描画位置をクリップする
|
||||
private static float clipTranslate(
|
||||
float view_w // ビューの幅
|
||||
, float bitmap_w // 画像の幅
|
||||
, float current_scale // 画像の拡大率
|
||||
, float trans_x // タッチ操作による表示位置
|
||||
){
|
||||
|
||||
// 余白(拡大率が小さい場合はプラス、拡大率が大きい場合はマイナス)
|
||||
float padding = view_w - bitmap_w * current_scale;
|
||||
|
||||
// 余白が>=0なら画像を中心に表示する。 <0なら操作された位置をクリップする。
|
||||
return padding >= 0f ? padding / 2f : clip( padding, 0f, trans_x );
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user