Compose M3 redesign wip
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,18 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.BulletSpan;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -12,28 +20,34 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.UpdateAttachment;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.parceler.Parcels;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import java.util.Collections;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
|
||||
public class ComposeImageDescriptionFragment extends MastodonToolbarFragment implements OnBackPressedListener{
|
||||
private static final String TAG="ComposeImageDescription";
|
||||
|
||||
private String accountID, attachmentID;
|
||||
private EditText edit;
|
||||
private Button saveButton;
|
||||
private FixedAspectRatioImageView image;
|
||||
private ContextThemeWrapper themeWrapper;
|
||||
private PhotoViewer photoViewer;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -46,7 +60,13 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.edit_image);
|
||||
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
|
||||
setTitle(R.string.add_alt_text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
return super.onCreateView(themeWrapper.getSystemService(LayoutInflater.class), container, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,14 +74,48 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
|
||||
View view=inflater.inflate(R.layout.fragment_image_description, container, false);
|
||||
|
||||
edit=view.findViewById(R.id.edit);
|
||||
ImageView image=view.findViewById(R.id.photo);
|
||||
image=view.findViewById(R.id.photo);
|
||||
int width=getArguments().getInt("width", 0);
|
||||
int height=getArguments().getInt("height", 0);
|
||||
if(width>0 && height>0){
|
||||
image.setAspectRatio(Math.max(1f, (float)width/height));
|
||||
}
|
||||
image.setOnClickListener(v->openPhotoViewer());
|
||||
Uri uri=getArguments().getParcelable("uri");
|
||||
ViewImageLoader.load(image, null, new UrlImageLoaderRequest(uri, 1000, 1000));
|
||||
Attachment.Type type=Attachment.Type.valueOf(getArguments().getString("attachmentType"));
|
||||
if(type==Attachment.Type.IMAGE)
|
||||
ViewImageLoader.load(image, null, new UrlImageLoaderRequest(uri, 1000, 1000));
|
||||
else
|
||||
loadVideoThumbIntoView(image, uri);
|
||||
edit.setText(getArguments().getString("existingDescription"));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void loadVideoThumbIntoView(ImageView target, Uri uri){
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
Context context=getActivity();
|
||||
if(context==null)
|
||||
return;
|
||||
try{
|
||||
MediaMetadataRetriever mmr=new MediaMetadataRetriever();
|
||||
mmr.setDataSource(context, uri);
|
||||
Bitmap frame=mmr.getFrameAtTime(3_000_000);
|
||||
mmr.release();
|
||||
int size=Math.max(frame.getWidth(), frame.getHeight());
|
||||
int maxSize=V.dp(250);
|
||||
if(size>maxSize){
|
||||
float factor=maxSize/(float)size;
|
||||
frame=Bitmap.createScaledBitmap(frame, Math.round(frame.getWidth()*factor), Math.round(frame.getHeight()*factor), true);
|
||||
}
|
||||
Bitmap finalFrame=frame;
|
||||
target.post(()->target.setImageBitmap(finalFrame));
|
||||
}catch(Exception x){
|
||||
Log.w(TAG, "loadVideoThumbIntoView: error getting video frame", x);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
@@ -71,43 +125,114 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||
TypedArray ta=getActivity().obtainStyledAttributes(new int[]{R.attr.secondaryButtonStyle});
|
||||
int buttonStyle=ta.getResourceId(0, 0);
|
||||
ta.recycle();
|
||||
saveButton=new Button(getActivity(), null, 0, buttonStyle);
|
||||
saveButton.setText(R.string.save);
|
||||
saveButton.setOnClickListener(this::onSaveClick);
|
||||
FrameLayout wrap=new FrameLayout(getActivity());
|
||||
wrap.addView(saveButton, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP|Gravity.LEFT));
|
||||
wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8));
|
||||
wrap.setClipToPadding(false);
|
||||
MenuItem item=menu.add(R.string.publish);
|
||||
item.setActionView(wrap);
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
inflater.inflate(R.menu.compose_image_description, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
if(item.getItemId()==R.id.help){
|
||||
SpannableStringBuilder msg=new SpannableStringBuilder(getText(R.string.alt_text_help));
|
||||
BulletSpan[] spans=msg.getSpans(0, msg.length(), BulletSpan.class);
|
||||
for(BulletSpan span:spans){
|
||||
BulletSpan betterSpan;
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.Q)
|
||||
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface));
|
||||
else
|
||||
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface), V.dp(1.5f));
|
||||
msg.setSpan(betterSpan, msg.getSpanStart(span), msg.getSpanEnd(span), msg.getSpanFlags(span));
|
||||
msg.removeSpan(span);
|
||||
}
|
||||
new M3AlertDialogBuilder(themeWrapper)
|
||||
.setTitle(R.string.what_is_alt_text)
|
||||
.setMessage(msg)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onSaveClick(View v){
|
||||
new UpdateAttachment(attachmentID, edit.getText().toString().trim())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Attachment result){
|
||||
Bundle r=new Bundle();
|
||||
r.putParcelable("attachment", Parcels.wrap(result));
|
||||
setResult(true, r);
|
||||
Nav.finish(ComposeImageDescriptionFragment.this);
|
||||
}
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
deliverResult();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.saving, false)
|
||||
.exec(accountID);
|
||||
@Override
|
||||
protected LayoutInflater getToolbarLayoutInflater(){
|
||||
return LayoutInflater.from(themeWrapper);
|
||||
}
|
||||
|
||||
private void deliverResult(){
|
||||
Bundle r=new Bundle();
|
||||
r.putString("text", edit.getText().toString().trim());
|
||||
r.putString("attachment", attachmentID);
|
||||
setResult(true, r);
|
||||
}
|
||||
|
||||
private void openPhotoViewer(){
|
||||
Attachment fakeAttachment=new Attachment();
|
||||
fakeAttachment.id="local";
|
||||
fakeAttachment.type=Attachment.Type.valueOf(getArguments().getString("attachmentType"));
|
||||
int width=getArguments().getInt("width", 0);
|
||||
int height=getArguments().getInt("height", 0);
|
||||
Uri uri=getArguments().getParcelable("uri");
|
||||
fakeAttachment.url=uri.toString();
|
||||
fakeAttachment.meta=new Attachment.Metadata();
|
||||
fakeAttachment.meta.width=width;
|
||||
fakeAttachment.meta.height=height;
|
||||
|
||||
photoViewer=new PhotoViewer(getActivity(), Collections.singletonList(fakeAttachment), 0, new PhotoViewer.Listener(){
|
||||
@Override
|
||||
public void setPhotoViewVisibility(int index, boolean visible){
|
||||
image.setAlpha(visible ? 1f : 0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
||||
int[] pos={0, 0};
|
||||
image.getLocationOnScreen(pos);
|
||||
outRect.set(pos[0], pos[1], pos[0]+image.getWidth(), pos[1]+image.getHeight());
|
||||
image.setElevation(1f);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransitioningViewTransform(float translateX, float translateY, float scale){
|
||||
image.setTranslationX(translateX);
|
||||
image.setTranslationY(translateY);
|
||||
image.setScaleX(scale);
|
||||
image.setScaleY(scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endPhotoViewTransition(){
|
||||
Drawable d=image.getDrawable();
|
||||
image.setImageDrawable(null);
|
||||
image.setImageDrawable(d);
|
||||
|
||||
image.setTranslationX(0f);
|
||||
image.setTranslationY(0f);
|
||||
image.setScaleX(1f);
|
||||
image.setScaleY(1f);
|
||||
image.setElevation(0f);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Drawable getPhotoViewCurrentDrawable(int index){
|
||||
return image.getDrawable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void photoViewerDismissed(){
|
||||
photoViewer=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissions(String[] permissions){
|
||||
|
||||
}
|
||||
});
|
||||
photoViewer.removeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,15 @@ import androidx.annotation.CallSuper;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
|
||||
public abstract class MastodonToolbarFragment extends ToolbarFragment{
|
||||
|
||||
public MastodonToolbarFragment(){
|
||||
super();
|
||||
}
|
||||
|
||||
protected MastodonToolbarFragment(int layout){
|
||||
super(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
@@ -387,6 +387,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state){
|
||||
if(isInEditMode)
|
||||
return;
|
||||
refreshLayout.setEnabled(state!=ViewPager2.SCROLL_STATE_DRAGGING);
|
||||
}
|
||||
});
|
||||
@@ -801,6 +803,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
bioEdit.setText(account.source.note);
|
||||
|
||||
aboutFragment.enterEditMode(account.source.fields);
|
||||
refreshLayout.setEnabled(false);
|
||||
}
|
||||
|
||||
private void exitEditMode(){
|
||||
@@ -840,6 +843,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
username.setVisibility(View.VISIBLE);
|
||||
bio.setVisibility(View.VISIBLE);
|
||||
countersLayout.setVisibility(View.VISIBLE);
|
||||
refreshLayout.setEnabled(true);
|
||||
|
||||
bindHeaderView();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
@@ -19,7 +21,6 @@ import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.SearchResults;
|
||||
import org.joinmastodon.android.ui.drawables.ComposeAutocompleteBackgroundDrawable;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -60,7 +61,6 @@ public class ComposeAutocompleteViewController{
|
||||
private APIRequest currentRequest;
|
||||
private Runnable usersDebouncer=this::doSearchUsers, hashtagsDebouncer=this::doSearchHashtags;
|
||||
private String lastText;
|
||||
private ComposeAutocompleteBackgroundDrawable background;
|
||||
private boolean listIsHidden=true;
|
||||
|
||||
private UsersAdapter usersAdapter;
|
||||
@@ -69,19 +69,25 @@ public class ComposeAutocompleteViewController{
|
||||
|
||||
private Consumer<String> completionSelectedListener;
|
||||
|
||||
private DividerItemDecoration usersDividers, hashtagsDividers;
|
||||
|
||||
public ComposeAutocompleteViewController(Activity activity, String accountID){
|
||||
this.activity=activity;
|
||||
this.accountID=accountID;
|
||||
background=new ComposeAutocompleteBackgroundDrawable(UiUtils.getThemeColor(activity, android.R.attr.colorBackground));
|
||||
contentView=new FrameLayout(activity);
|
||||
contentView.setBackground(background);
|
||||
|
||||
list=new UsableRecyclerView(activity);
|
||||
list.setLayoutManager(new LinearLayoutManager(activity));
|
||||
list.setLayoutManager(new LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false));
|
||||
list.setItemAnimator(new BetterItemAnimator());
|
||||
list.setVisibility(View.GONE);
|
||||
list.setPadding(V.dp(16), V.dp(12), V.dp(16), V.dp(12));
|
||||
list.setClipToPadding(false);
|
||||
list.setSelector(null);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
if(parent.getChildAdapterPosition(view)<parent.getAdapter().getItemCount()-1)
|
||||
outRect.right=V.dp(8);
|
||||
}
|
||||
});
|
||||
contentView.addView(list, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
progress=new ProgressBar(activity);
|
||||
@@ -89,9 +95,6 @@ public class ComposeAutocompleteViewController{
|
||||
progressLP.topMargin=V.dp(16);
|
||||
contentView.addView(progress, progressLP);
|
||||
|
||||
usersDividers=new DividerItemDecoration(activity, R.attr.colorPollVoted, 1, 72, 16);
|
||||
hashtagsDividers=new DividerItemDecoration(activity, R.attr.colorPollVoted, 1, 16, 16);
|
||||
|
||||
imgLoader=new ListImageLoaderWrapper(activity, list, new RecyclerViewDelegate(list), null);
|
||||
}
|
||||
|
||||
@@ -141,11 +144,6 @@ public class ComposeAutocompleteViewController{
|
||||
progress.setVisibility(View.GONE);
|
||||
listIsHidden=false;
|
||||
}
|
||||
if((prevMode==Mode.HASHTAGS)!=(mode==Mode.HASHTAGS) || prevMode==null){
|
||||
if(prevMode!=null)
|
||||
list.removeItemDecoration(prevMode==Mode.HASHTAGS ? hashtagsDividers : usersDividers);
|
||||
list.addItemDecoration(mode==Mode.HASHTAGS ? hashtagsDividers : usersDividers);
|
||||
}
|
||||
}
|
||||
lastText=text;
|
||||
if(mode==Mode.USERS){
|
||||
@@ -176,10 +174,6 @@ public class ComposeAutocompleteViewController{
|
||||
this.completionSelectedListener=completionSelectedListener;
|
||||
}
|
||||
|
||||
public void setArrowOffset(int offset){
|
||||
background.setArrowOffset(offset);
|
||||
}
|
||||
|
||||
public View getView(){
|
||||
return contentView;
|
||||
}
|
||||
@@ -258,7 +252,7 @@ public class ComposeAutocompleteViewController{
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return 1+users.get(position).emojiHelper.getImageCount();
|
||||
return 1/*+users.get(position).emojiHelper.getImageCount()*/;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -272,20 +266,18 @@ public class ComposeAutocompleteViewController{
|
||||
|
||||
private class UserViewHolder extends BindableViewHolder<WrappedAccount> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
private final ImageView ava;
|
||||
private final TextView name, username;
|
||||
private final TextView username;
|
||||
|
||||
private UserViewHolder(){
|
||||
super(activity, R.layout.item_autocomplete_user, list);
|
||||
ava=findViewById(R.id.photo);
|
||||
name=findViewById(R.id.name);
|
||||
username=findViewById(R.id.username);
|
||||
ava.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
ava.setOutlineProvider(OutlineProviders.OVAL);
|
||||
ava.setClipToOutline(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(WrappedAccount item){
|
||||
name.setText(item.parsedName);
|
||||
username.setText("@"+item.account.acct);
|
||||
}
|
||||
|
||||
@@ -300,7 +292,6 @@ public class ComposeAutocompleteViewController{
|
||||
ava.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-1, image);
|
||||
name.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,17 +324,11 @@ public class ComposeAutocompleteViewController{
|
||||
private final TextView text;
|
||||
|
||||
private HashtagViewHolder(){
|
||||
super(new TextView(activity));
|
||||
super(activity, R.layout.item_autocomplete_hashtag, list);
|
||||
text=(TextView) itemView;
|
||||
text.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(48)));
|
||||
text.setTextAppearance(R.style.m3_title_medium);
|
||||
text.setTypeface(Typeface.DEFAULT);
|
||||
text.setSingleLine();
|
||||
text.setEllipsize(TextUtils.TruncateAt.END);
|
||||
text.setGravity(Gravity.CENTER_VERTICAL);
|
||||
text.setPadding(V.dp(16), 0, V.dp(16), 0);
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onBind(Hashtag item){
|
||||
text.setText("#"+item.name);
|
||||
@@ -395,7 +380,7 @@ public class ComposeAutocompleteViewController{
|
||||
private EmojiViewHolder(){
|
||||
super(activity, R.layout.item_autocomplete_user, list);
|
||||
ava=findViewById(R.id.photo);
|
||||
name=findViewById(R.id.name);
|
||||
name=findViewById(R.id.username);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -408,6 +393,7 @@ public class ComposeAutocompleteViewController{
|
||||
ava.setImageDrawable(null);
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onBind(WrappedEmoji item){
|
||||
name.setText(":"+item.emoji.shortcode+":");
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package org.joinmastodon.android.ui.drawables;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ComposeAutocompleteBackgroundDrawable extends Drawable{
|
||||
private Path path=new Path();
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private int fillColor, arrowOffset;
|
||||
|
||||
public ComposeAutocompleteBackgroundDrawable(int fillColor){
|
||||
this.fillColor=fillColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas){
|
||||
Rect bounds=getBounds();
|
||||
canvas.save();
|
||||
canvas.translate(bounds.left, bounds.top);
|
||||
paint.setColor(0x80000000);
|
||||
canvas.drawPath(path, paint);
|
||||
canvas.translate(0, V.dp(1));
|
||||
paint.setColor(fillColor);
|
||||
canvas.drawPath(path, paint);
|
||||
int arrowSize=V.dp(10);
|
||||
canvas.drawRect(0, arrowSize, bounds.width(), bounds.height(), paint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity(){
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
public void setArrowOffset(int offset){
|
||||
arrowOffset=offset;
|
||||
updatePath();
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds){
|
||||
super.onBoundsChange(bounds);
|
||||
updatePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPadding(@NonNull Rect padding){
|
||||
padding.top=V.dp(11);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updatePath(){
|
||||
path.rewind();
|
||||
int arrowSize=V.dp(10);
|
||||
path.moveTo(0, arrowSize*2);
|
||||
path.lineTo(0, arrowSize);
|
||||
path.lineTo(arrowOffset-arrowSize, arrowSize);
|
||||
path.lineTo(arrowOffset, 0);
|
||||
path.lineTo(arrowOffset+arrowSize, arrowSize);
|
||||
path.lineTo(getBounds().width(), arrowSize);
|
||||
path.lineTo(getBounds().width(), arrowSize*2);
|
||||
path.close();
|
||||
}
|
||||
}
|
||||
@@ -259,6 +259,10 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
});
|
||||
}
|
||||
|
||||
public void removeMenu(){
|
||||
toolbar.getMenu().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionAnimationUpdate(float translateX, float translateY, float scale){
|
||||
listener.setTransitioningViewTransform(translateX, translateY, scale);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.joinmastodon.android.ui.text;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
public class LengthLimitHighlighter implements TextWatcher{
|
||||
private final Context context;
|
||||
private final int lengthLimit;
|
||||
private BackgroundColorSpan overLimitBG;
|
||||
private ForegroundColorSpan overLimitFG;
|
||||
private boolean isOverLimit;
|
||||
private OverLimitChangeListener listener;
|
||||
|
||||
public LengthLimitHighlighter(Context context, int lengthLimit){
|
||||
this.context=context;
|
||||
overLimitBG=new BackgroundColorSpan(UiUtils.getThemeColor(context, R.attr.colorM3ErrorContainer));
|
||||
overLimitFG=new ForegroundColorSpan(UiUtils.getThemeColor(context, R.attr.colorM3Error));
|
||||
this.lengthLimit=lengthLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
s.removeSpan(overLimitBG);
|
||||
s.removeSpan(overLimitFG);
|
||||
boolean newOverLimit=s.length()>lengthLimit;
|
||||
if(newOverLimit){
|
||||
int start=s.length()-(s.length()-lengthLimit);
|
||||
int end=s.length();
|
||||
s.setSpan(overLimitFG, start, end, 0);
|
||||
s.setSpan(overLimitBG, start, end, 0);
|
||||
}
|
||||
if(newOverLimit!=isOverLimit){
|
||||
isOverLimit=newOverLimit;
|
||||
if(listener!=null)
|
||||
listener.onOverLimitChanged(isOverLimit);
|
||||
}
|
||||
}
|
||||
|
||||
public LengthLimitHighlighter setListener(OverLimitChangeListener listener){
|
||||
this.listener=listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isOverLimit(){
|
||||
return isOverLimit;
|
||||
}
|
||||
|
||||
public interface OverLimitChangeListener{
|
||||
void onOverLimitChanged(boolean isOverLimit);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
@@ -27,9 +26,15 @@ import android.provider.OpenableColumns;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.transition.ChangeBounds;
|
||||
import android.transition.ChangeScroll;
|
||||
import android.transition.Fade;
|
||||
import android.transition.TransitionManager;
|
||||
import android.transition.TransitionSet;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Button;
|
||||
import android.widget.PopupMenu;
|
||||
@@ -86,6 +91,7 @@ import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import okhttp3.MediaType;
|
||||
|
||||
@@ -642,6 +648,10 @@ public class UiUtils{
|
||||
return 0xFF000000 | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
public static int alphaBlendThemeColors(Context context, @AttrRes int color1, @AttrRes int color2, float alpha){
|
||||
return alphaBlendColors(getThemeColor(context, color1), getThemeColor(context, color2), alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if Android platform photopicker is available on the device\
|
||||
*
|
||||
@@ -713,4 +723,14 @@ public class UiUtils{
|
||||
else
|
||||
return String.format("%d:%02d", seconds/60, seconds%60);
|
||||
}
|
||||
|
||||
public static void beginLayoutTransition(ViewGroup sceneRoot){
|
||||
TransitionManager.beginDelayedTransition(sceneRoot, new TransitionSet()
|
||||
.addTransition(new Fade(Fade.IN | Fade.OUT))
|
||||
.addTransition(new ChangeBounds())
|
||||
.addTransition(new ChangeScroll())
|
||||
.setDuration(250)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
public class CheckableLinearLayout extends LinearLayout implements Checkable{
|
||||
private boolean checked;
|
||||
private static final int[] CHECKED_STATE_SET = {
|
||||
android.R.attr.state_checked
|
||||
};
|
||||
|
||||
public CheckableLinearLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CheckableLinearLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean checked){
|
||||
this.checked=checked;
|
||||
refreshDrawableState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked(){
|
||||
return checked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggle(){
|
||||
setChecked(!checked);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
if (isChecked()) {
|
||||
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
|
||||
}
|
||||
return drawableState;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
public class FixedAspectRatioImageView extends ImageView{
|
||||
private float aspectRatio=1;
|
||||
|
||||
public FixedAspectRatioImageView(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public FixedAspectRatioImageView(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public FixedAspectRatioImageView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
int width=MeasureSpec.getSize(widthMeasureSpec);
|
||||
heightMeasureSpec=Math.round(width/aspectRatio) | MeasureSpec.EXACTLY;
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
public float getAspectRatio(){
|
||||
return aspectRatio;
|
||||
}
|
||||
|
||||
public void setAspectRatio(float aspectRatio){
|
||||
this.aspectRatio=aspectRatio;
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,12 @@ public class FloatingHintEditTextLayout extends FrameLayout implements CustomVie
|
||||
edit.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
|
||||
float scale=edit.getLineHeight()/(float)label.getLineHeight();
|
||||
float transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
|
||||
float transY;
|
||||
if((edit.getGravity() & Gravity.TOP)==Gravity.TOP){
|
||||
transY=edit.getPaddingTop()+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
|
||||
}else{
|
||||
transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
|
||||
}
|
||||
|
||||
AnimatorSet anim=new AnimatorSet();
|
||||
if(hintVisible){
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.HorizontalScrollView;
|
||||
|
||||
public class HorizontalScrollViewThatRespectsMatchParent extends HorizontalScrollView{
|
||||
public HorizontalScrollViewThatRespectsMatchParent(Context context){
|
||||
super(context);
|
||||
}
|
||||
|
||||
public HorizontalScrollViewThatRespectsMatchParent(Context context, AttributeSet attrs){
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public HorizontalScrollViewThatRespectsMatchParent(Context context, AttributeSet attrs, int defStyleAttr){
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
if(getChildCount()==0)
|
||||
return;
|
||||
View child=getChildAt(0);
|
||||
ViewGroup.LayoutParams lp=child.getLayoutParams();
|
||||
if(lp.width==ViewGroup.LayoutParams.MATCH_PARENT){
|
||||
int hms=getChildMeasureSpec(heightMeasureSpec, getPaddingTop()+getPaddingBottom(), lp.height);
|
||||
child.measure(MeasureSpec.getSize(widthMeasureSpec) | MeasureSpec.EXACTLY, hms);
|
||||
setMeasuredDimension(child.getMeasuredWidth(), child.getMeasuredHeight());
|
||||
return;
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
@@ -6,19 +6,48 @@ import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.CustomViewHelper;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ReorderableLinearLayout extends LinearLayout{
|
||||
public class ReorderableLinearLayout extends LinearLayout implements CustomViewHelper{
|
||||
private static final String TAG="ReorderableLinearLayout";
|
||||
|
||||
private static final Interpolator sDragScrollInterpolator=t->t * t * t * t * t;
|
||||
|
||||
private static final Interpolator sDragViewScrollCapInterpolator=t->{
|
||||
t -= 1.0f;
|
||||
return t * t * t * t * t + 1.0f;
|
||||
};
|
||||
private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000;
|
||||
|
||||
private View draggedView;
|
||||
private View bottomSibling, topSibling;
|
||||
private float startY;
|
||||
private float startX, startY, dX, dY, viewStartX, viewStartY;
|
||||
private OnDragListener dragListener;
|
||||
private boolean moveInBothDimensions;
|
||||
private int edgeSize;
|
||||
private View scrollableParent;
|
||||
private long dragScrollStartTime;
|
||||
private int cachedMaxScrollSpeed=-1;
|
||||
final Runnable scrollRunnable= new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (draggedView != null && scrollIfNecessary()) {
|
||||
if (draggedView != null) { //it might be lost during scrolling
|
||||
// moveIfNecessary(mSelected);
|
||||
}
|
||||
removeCallbacks(scrollRunnable);
|
||||
postOnAnimation(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public ReorderableLinearLayout(Context context){
|
||||
super(context);
|
||||
@@ -30,12 +59,13 @@ public class ReorderableLinearLayout extends LinearLayout{
|
||||
|
||||
public ReorderableLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr){
|
||||
super(context, attrs, defStyleAttr);
|
||||
edgeSize=dp(20);
|
||||
}
|
||||
|
||||
public void startDragging(View child){
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
draggedView=child;
|
||||
draggedView.animate().translationZ(V.dp(1f)).setDuration(150).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
dragListener.onDragStart(draggedView);
|
||||
|
||||
int index=indexOfChild(child);
|
||||
if(index==-1)
|
||||
@@ -44,11 +74,30 @@ public class ReorderableLinearLayout extends LinearLayout{
|
||||
topSibling=getChildAt(index-1);
|
||||
if(index<getChildCount()-1)
|
||||
bottomSibling=getChildAt(index+1);
|
||||
|
||||
scrollableParent=findScrollableParent(this);
|
||||
|
||||
viewStartX=child.getX();
|
||||
viewStartY=child.getY();
|
||||
}
|
||||
|
||||
private View findScrollableParent(View child){
|
||||
if(getOrientation()==VERTICAL){
|
||||
if(child.canScrollVertically(-1) || child.canScrollVertically(1))
|
||||
return child;
|
||||
}else{
|
||||
if(child.canScrollHorizontally(-1) || child.canScrollHorizontally(1))
|
||||
return child;
|
||||
}
|
||||
if(child.getParent() instanceof View v)
|
||||
return findScrollableParent(v);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev){
|
||||
if(draggedView!=null){
|
||||
startX=ev.getX();
|
||||
startY=ev.getY();
|
||||
return true;
|
||||
}
|
||||
@@ -60,34 +109,61 @@ public class ReorderableLinearLayout extends LinearLayout{
|
||||
if(draggedView!=null){
|
||||
if(ev.getAction()==MotionEvent.ACTION_UP || ev.getAction()==MotionEvent.ACTION_CANCEL){
|
||||
endDrag();
|
||||
removeCallbacks(scrollRunnable);
|
||||
draggedView=null;
|
||||
bottomSibling=null;
|
||||
topSibling=null;
|
||||
}else if(ev.getAction()==MotionEvent.ACTION_MOVE){
|
||||
draggedView.setTranslationY(ev.getY()-startY);
|
||||
if(topSibling!=null && draggedView.getY()<=topSibling.getY()){
|
||||
moveDraggedView(-1);
|
||||
}else if(bottomSibling!=null && draggedView.getY()>=bottomSibling.getY()){
|
||||
moveDraggedView(1);
|
||||
dX=ev.getX()-startX;
|
||||
dY=ev.getY()-startY;
|
||||
|
||||
if(moveInBothDimensions){
|
||||
draggedView.setTranslationX(dX);
|
||||
draggedView.setTranslationY(dY);
|
||||
}else if(getOrientation()==VERTICAL){
|
||||
draggedView.setTranslationY(dY);
|
||||
}else{
|
||||
draggedView.setTranslationX(dX);
|
||||
}
|
||||
|
||||
removeCallbacks(scrollRunnable);
|
||||
scrollRunnable.run();
|
||||
|
||||
if(getOrientation()==VERTICAL){
|
||||
if(topSibling!=null && draggedView.getY()<=topSibling.getY()){
|
||||
moveDraggedView(-1);
|
||||
}else if(bottomSibling!=null && draggedView.getY()>=bottomSibling.getY()){
|
||||
moveDraggedView(1);
|
||||
}
|
||||
}else{
|
||||
if(topSibling!=null && draggedView.getX()<=topSibling.getX()){
|
||||
moveDraggedView(-1);
|
||||
}else if(bottomSibling!=null && draggedView.getX()>=bottomSibling.getX()){
|
||||
moveDraggedView(1);
|
||||
}
|
||||
}
|
||||
dragListener.onDragMove(draggedView);
|
||||
}
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
private void endDrag(){
|
||||
draggedView.animate().translationY(0f).translationZ(0f).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
dragListener.onDragEnd(draggedView);
|
||||
}
|
||||
|
||||
private void moveDraggedView(int positionOffset){
|
||||
int index=indexOfChild(draggedView);
|
||||
int prevTop=draggedView.getTop();
|
||||
|
||||
boolean isVertical=getOrientation()==VERTICAL;
|
||||
|
||||
int prevOffset=isVertical ? draggedView.getTop() : draggedView.getLeft();
|
||||
removeView(draggedView);
|
||||
int prevIndex=index;
|
||||
index+=positionOffset;
|
||||
addView(draggedView, index);
|
||||
final View prevSibling=positionOffset<0 ? topSibling : bottomSibling;
|
||||
int prevSiblingTop=prevSibling.getTop();
|
||||
int prevSiblingOffset=isVertical ? prevSibling.getTop() : prevSibling.getLeft();
|
||||
if(index>0)
|
||||
topSibling=getChildAt(index-1);
|
||||
else
|
||||
@@ -101,11 +177,20 @@ public class ReorderableLinearLayout extends LinearLayout{
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
draggedView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
float offset=prevTop-draggedView.getTop();
|
||||
startY-=offset;
|
||||
draggedView.setTranslationY(draggedView.getTranslationY()+offset);
|
||||
prevSibling.setTranslationY(prevSiblingTop-prevSibling.getTop());
|
||||
prevSibling.animate().translationY(0f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(200).start();
|
||||
float offset=prevOffset-(isVertical ? draggedView.getTop() : draggedView.getLeft());
|
||||
if(isVertical){
|
||||
startY-=offset;
|
||||
viewStartY-=offset;
|
||||
draggedView.setTranslationY(draggedView.getTranslationY()+offset);
|
||||
prevSibling.setTranslationY(prevSiblingOffset-prevSibling.getTop());
|
||||
prevSibling.animate().translationY(0f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(200).start();
|
||||
}else{
|
||||
startX-=offset;
|
||||
viewStartX-=offset;
|
||||
draggedView.setTranslationX(draggedView.getTranslationX()+offset);
|
||||
prevSibling.setTranslationX(prevSiblingOffset-prevSibling.getLeft());
|
||||
prevSibling.animate().translationX(0f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(200).start();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -115,7 +200,105 @@ public class ReorderableLinearLayout extends LinearLayout{
|
||||
this.dragListener=dragListener;
|
||||
}
|
||||
|
||||
public boolean isMoveInBothDimensions(){
|
||||
return moveInBothDimensions;
|
||||
}
|
||||
|
||||
public void setMoveInBothDimensions(boolean moveInBothDimensions){
|
||||
this.moveInBothDimensions=moveInBothDimensions;
|
||||
}
|
||||
|
||||
boolean scrollIfNecessary(){
|
||||
if(draggedView==null || scrollableParent==null){
|
||||
dragScrollStartTime=Long.MIN_VALUE;
|
||||
return false;
|
||||
}
|
||||
final long now=System.currentTimeMillis();
|
||||
final long scrollDuration=dragScrollStartTime==Long.MIN_VALUE ? 0 : now-dragScrollStartTime;
|
||||
int scrollX=0;
|
||||
int scrollY=0;
|
||||
if(getOrientation()==HORIZONTAL){
|
||||
int curX=(int) (viewStartX+dX)-scrollableParent.getScrollX();
|
||||
final int leftDiff=curX-getPaddingLeft();
|
||||
if(dX<0 && leftDiff<0){
|
||||
scrollX=leftDiff;
|
||||
}else if(dX>0){
|
||||
final int rightDiff=curX+draggedView.getWidth()-(scrollableParent.getWidth()-getPaddingRight());
|
||||
if(rightDiff>0){
|
||||
scrollX=rightDiff;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
int curY=(int) (viewStartY+dY)-scrollableParent.getScrollY();
|
||||
final int topDiff=curY-getPaddingTop();
|
||||
if(dY<0 && topDiff<0){
|
||||
scrollY=topDiff;
|
||||
}else if(dY>0){
|
||||
final int bottomDiff=curY+draggedView.getHeight()-(scrollableParent.getHeight()-getPaddingBottom());
|
||||
if(bottomDiff>0){
|
||||
scrollY=bottomDiff;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(scrollX!=0){
|
||||
scrollX=interpolateOutOfBoundsScroll(draggedView.getWidth(), scrollX, scrollableParent.getWidth(), scrollDuration);
|
||||
}
|
||||
if(scrollY!=0){
|
||||
scrollY=interpolateOutOfBoundsScroll(draggedView.getHeight(), scrollY, scrollableParent.getHeight(), scrollDuration);
|
||||
}
|
||||
if(scrollX!=0 || scrollY!=0){
|
||||
if(dragScrollStartTime==Long.MIN_VALUE){
|
||||
dragScrollStartTime=now;
|
||||
}
|
||||
int prevX=scrollableParent.getScrollX();
|
||||
int prevY=scrollableParent.getScrollY();
|
||||
scrollableParent.scrollBy(scrollX, scrollY);
|
||||
draggedView.setTranslationX(draggedView.getTranslationX()-(scrollableParent.getScrollX()-prevX));
|
||||
draggedView.setTranslationY(draggedView.getTranslationY()-(scrollableParent.getScrollY()-prevY));
|
||||
return true;
|
||||
}
|
||||
dragScrollStartTime=Long.MIN_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int interpolateOutOfBoundsScroll(int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll){
|
||||
final int maxScroll=getMaxDragScroll();
|
||||
final int absOutOfBounds=Math.abs(viewSizeOutOfBounds);
|
||||
final int direction=(int) Math.signum(viewSizeOutOfBounds);
|
||||
// might be negative if other direction
|
||||
float outOfBoundsRatio=Math.min(1f, 1f*absOutOfBounds/viewSize);
|
||||
final int cappedScroll=(int) (direction*maxScroll*sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio));
|
||||
final float timeRatio;
|
||||
if(msSinceStartScroll>DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS){
|
||||
timeRatio=1f;
|
||||
}else{
|
||||
timeRatio=(float) msSinceStartScroll/DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS;
|
||||
}
|
||||
final int value=(int) (cappedScroll*sDragScrollInterpolator.getInterpolation(timeRatio));
|
||||
if(value==0){
|
||||
return viewSizeOutOfBounds>0 ? 1 : -1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private int getMaxDragScroll(){
|
||||
if(cachedMaxScrollSpeed==-1){
|
||||
cachedMaxScrollSpeed=getResources().getDimensionPixelSize(R.dimen.item_touch_helper_max_drag_scroll_per_frame);
|
||||
}
|
||||
return cachedMaxScrollSpeed;
|
||||
}
|
||||
|
||||
public interface OnDragListener{
|
||||
void onSwapItems(int oldIndex, int newIndex);
|
||||
|
||||
default void onDragStart(View view){
|
||||
view.animate().translationZ(V.dp(3f)).setDuration(150).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
}
|
||||
|
||||
default void onDragEnd(View view){
|
||||
view.animate().translationY(0f).translationX(0f).translationZ(0f).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
}
|
||||
|
||||
default void onDragMove(View view){}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user