Compose things
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
|
||||
public class UpdateAttachment extends MastodonAPIRequest<Attachment>{
|
||||
public UpdateAttachment(String id, String description){
|
||||
super(HttpMethod.PUT, "/media/"+id, Attachment.class);
|
||||
setRequestBody(new Body(description));
|
||||
}
|
||||
|
||||
private static class Body{
|
||||
public String description;
|
||||
|
||||
public Body(String description){
|
||||
this.description=description;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import android.icu.text.BreakIterator;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
@@ -33,6 +34,7 @@ import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.twitter.twittertext.Regex;
|
||||
import com.twitter.twittertext.TwitterTextEmojiRegex;
|
||||
@@ -58,8 +60,11 @@ import org.joinmastodon.android.ui.PopupKeyboard;
|
||||
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
|
||||
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
|
||||
import org.joinmastodon.android.ui.views.SizeListenerLinearLayout;
|
||||
import org.parceler.Parcel;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -80,7 +85,9 @@ import me.grishka.appkit.utils.V;
|
||||
public class ComposeFragment extends ToolbarFragment implements OnBackPressedListener{
|
||||
|
||||
private static final int MEDIA_RESULT=717;
|
||||
private static final int IMAGE_DESCRIPTION_RESULT=363;
|
||||
private static final int MAX_POLL_OPTIONS=4;
|
||||
private static final int MAX_ATTACHMENTS=4;
|
||||
|
||||
private static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
@@ -115,7 +122,7 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
||||
|
||||
private Button publishButton;
|
||||
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn;
|
||||
private LinearLayout attachmentsView;
|
||||
private ComposeMediaLayout attachmentsView;
|
||||
private TextView replyText;
|
||||
private ReorderableLinearLayout pollOptionsView;
|
||||
private View pollWrap;
|
||||
@@ -124,7 +131,7 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
||||
|
||||
private ArrayList<DraftPollOption> pollOptions=new ArrayList<>();
|
||||
|
||||
private ArrayList<DraftMediaAttachment> queuedAttachments=new ArrayList<>(), failedAttachments=new ArrayList<>(), attachments=new ArrayList<>();
|
||||
private ArrayList<DraftMediaAttachment> queuedAttachments=new ArrayList<>(), failedAttachments=new ArrayList<>(), attachments=new ArrayList<>(), allAttachments=new ArrayList<>();
|
||||
private DraftMediaAttachment uploadingAttachment;
|
||||
|
||||
private List<EmojiCategory> customEmojis;
|
||||
@@ -244,7 +251,20 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
||||
spoilerBtn.setSelected(true);
|
||||
}
|
||||
|
||||
// TODO save and restore media attachments (when design is ready)
|
||||
if(savedInstanceState!=null && savedInstanceState.containsKey("attachments")){
|
||||
ArrayList<Parcelable> serializedAttachments=savedInstanceState.getParcelableArrayList("attachments");
|
||||
for(Parcelable a:serializedAttachments){
|
||||
DraftMediaAttachment att=Parcels.unwrap(a);
|
||||
attachmentsView.addView(createMediaAttachmentView(att));
|
||||
attachments.add(att);
|
||||
}
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
}else if(!allAttachments.isEmpty()){
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
for(DraftMediaAttachment att:allAttachments){
|
||||
attachmentsView.addView(createMediaAttachmentView(att));
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -261,6 +281,13 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
||||
outState.putInt("pollDuration", pollDuration);
|
||||
outState.putString("pollDurationStr", pollDurationStr);
|
||||
outState.putBoolean("hasSpoiler", hasSpoiler);
|
||||
if(!attachments.isEmpty()){
|
||||
ArrayList<Parcelable> serializedAttachments=new ArrayList<>(attachments.size());
|
||||
for(DraftMediaAttachment att:attachments){
|
||||
serializedAttachments.add(Parcels.wrap(att));
|
||||
}
|
||||
outState.putParcelableArrayList("attachments", serializedAttachments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,6 +500,21 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||
if(reqCode==IMAGE_DESCRIPTION_RESULT && success){
|
||||
Attachment updated=Parcels.unwrap(result.getParcelable("attachment"));
|
||||
for(DraftMediaAttachment att:attachments){
|
||||
if(att.serverAttachment.id.equals(updated.id)){
|
||||
att.serverAttachment=updated;
|
||||
att.description=updated.description;
|
||||
att.descriptionView.setText(att.description);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void confirmDiscardDraftAndFinish(){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.discard_draft)
|
||||
@@ -506,26 +548,61 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
||||
}
|
||||
|
||||
private void addMediaAttachment(Uri uri){
|
||||
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS)
|
||||
return;
|
||||
pollBtn.setEnabled(false);
|
||||
View thumb=getActivity().getLayoutInflater().inflate(R.layout.compose_media_thumb, attachmentsView, false);
|
||||
ImageView img=thumb.findViewById(R.id.thumb);
|
||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(uri, V.dp(250), V.dp(250)));
|
||||
attachmentsView.addView(thumb);
|
||||
|
||||
DraftMediaAttachment draft=new DraftMediaAttachment();
|
||||
draft.uri=uri;
|
||||
draft.view=thumb;
|
||||
draft.progressBar=thumb.findViewById(R.id.progress);
|
||||
Button btn=thumb.findViewById(R.id.remove_btn);
|
||||
btn.setTag(draft);
|
||||
btn.setOnClickListener(this::onRemoveMediaAttachmentClick);
|
||||
|
||||
attachmentsView.addView(createMediaAttachmentView(draft));
|
||||
allAttachments.add(draft);
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
if(uploadingAttachment==null){
|
||||
uploadMediaAttachment(draft);
|
||||
}else{
|
||||
queuedAttachments.add(draft);
|
||||
}
|
||||
updatePublishButtonState();
|
||||
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS)
|
||||
mediaBtn.setEnabled(false);
|
||||
}
|
||||
|
||||
private View createMediaAttachmentView(DraftMediaAttachment draft){
|
||||
View thumb=getActivity().getLayoutInflater().inflate(R.layout.compose_media_thumb, attachmentsView, false);
|
||||
ImageView img=thumb.findViewById(R.id.thumb);
|
||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(draft.uri, V.dp(250), V.dp(250)));
|
||||
TextView fileName=thumb.findViewById(R.id.file_name);
|
||||
fileName.setText(UiUtils.getFileName(draft.uri));
|
||||
|
||||
draft.view=thumb;
|
||||
draft.progressBar=thumb.findViewById(R.id.progress);
|
||||
draft.infoBar=thumb.findViewById(R.id.info_bar);
|
||||
draft.errorOverlay=thumb.findViewById(R.id.error_overlay);
|
||||
draft.descriptionView=thumb.findViewById(R.id.description);
|
||||
ImageButton btn=thumb.findViewById(R.id.remove_btn);
|
||||
btn.setTag(draft);
|
||||
btn.setOnClickListener(this::onRemoveMediaAttachmentClick);
|
||||
btn=thumb.findViewById(R.id.remove_btn2);
|
||||
btn.setTag(draft);
|
||||
btn.setOnClickListener(this::onRemoveMediaAttachmentClick);
|
||||
Button retry=thumb.findViewById(R.id.retry_upload);
|
||||
retry.setTag(draft);
|
||||
retry.setOnClickListener(this::onRetryMediaUploadClick);
|
||||
draft.infoBar.setTag(draft);
|
||||
draft.infoBar.setOnClickListener(this::onEditMediaDescriptionClick);
|
||||
|
||||
if(!TextUtils.isEmpty(draft.description))
|
||||
draft.descriptionView.setText(draft.description);
|
||||
|
||||
if(uploadingAttachment!=draft && !queuedAttachments.contains(draft)){
|
||||
draft.progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
if(failedAttachments.contains(draft)){
|
||||
draft.infoBar.setVisibility(View.GONE);
|
||||
draft.errorOverlay.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
return thumb;
|
||||
}
|
||||
|
||||
private void uploadMediaAttachment(DraftMediaAttachment attachment){
|
||||
@@ -561,8 +638,11 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
||||
attachment.uploadRequest=null;
|
||||
uploadingAttachment=null;
|
||||
failedAttachments.add(attachment);
|
||||
error.showToast(getActivity());
|
||||
// TODO show the error state in the attachment view
|
||||
// error.showToast(getActivity());
|
||||
Toast.makeText(getActivity(), R.string.image_upload_failed, Toast.LENGTH_SHORT).show();
|
||||
|
||||
V.setVisibilityAnimated(attachment.errorOverlay, View.VISIBLE);
|
||||
V.setVisibilityAnimated(attachment.infoBar, View.GONE);
|
||||
|
||||
if(!queuedAttachments.isEmpty())
|
||||
uploadMediaAttachment(queuedAttachments.remove(0));
|
||||
@@ -584,9 +664,38 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
||||
queuedAttachments.remove(att);
|
||||
failedAttachments.remove(att);
|
||||
}
|
||||
allAttachments.remove(att);
|
||||
attachmentsView.removeView(att.view);
|
||||
if(getMediaAttachmentsCount()==0)
|
||||
attachmentsView.setVisibility(View.GONE);
|
||||
updatePublishButtonState();
|
||||
pollBtn.setEnabled(attachments.isEmpty() && queuedAttachments.isEmpty() && failedAttachments.isEmpty() && uploadingAttachment==null);
|
||||
mediaBtn.setEnabled(true);
|
||||
}
|
||||
|
||||
private void onRetryMediaUploadClick(View v){
|
||||
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
|
||||
if(failedAttachments.remove(att)){
|
||||
V.setVisibilityAnimated(att.errorOverlay, View.GONE);
|
||||
V.setVisibilityAnimated(att.infoBar, View.VISIBLE);
|
||||
V.setVisibilityAnimated(att.progressBar, View.VISIBLE);
|
||||
if(uploadingAttachment==null)
|
||||
uploadMediaAttachment(att);
|
||||
else
|
||||
queuedAttachments.add(att);
|
||||
}
|
||||
}
|
||||
|
||||
private void onEditMediaDescriptionClick(View v){
|
||||
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
|
||||
if(att.serverAttachment==null)
|
||||
return;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("attachment", att.serverAttachment.id);
|
||||
args.putParcelable("uri", att.uri);
|
||||
args.putString("existingDescription", att.description);
|
||||
Nav.goForResult(getActivity(), ComposeImageDescriptionFragment.class, args, IMAGE_DESCRIPTION_RESULT, this);
|
||||
}
|
||||
|
||||
private void togglePoll(){
|
||||
@@ -680,13 +789,22 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
||||
}
|
||||
}
|
||||
|
||||
private static class DraftMediaAttachment{
|
||||
private int getMediaAttachmentsCount(){
|
||||
return allAttachments.size();
|
||||
}
|
||||
|
||||
@Parcel
|
||||
static class DraftMediaAttachment{
|
||||
public Attachment serverAttachment;
|
||||
public Uri uri;
|
||||
public UploadAttachment uploadRequest;
|
||||
public transient UploadAttachment uploadRequest;
|
||||
public String description;
|
||||
|
||||
public View view;
|
||||
public ProgressBar progressBar;
|
||||
public transient View view;
|
||||
public transient ProgressBar progressBar;
|
||||
public transient TextView descriptionView;
|
||||
public transient View errorOverlay;
|
||||
public transient View infoBar;
|
||||
}
|
||||
|
||||
private static class DraftPollOption{
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.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.model.Attachment;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ComposeImageDescriptionFragment extends ToolbarFragment{
|
||||
private String accountID, attachmentID;
|
||||
private EditText edit;
|
||||
private Button saveButton;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
attachmentID=getArguments().getString("attachment");
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.edit_image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_image_description, container, false);
|
||||
|
||||
edit=view.findViewById(R.id.edit);
|
||||
ImageView image=view.findViewById(R.id.photo);
|
||||
Uri uri=getArguments().getParcelable("uri");
|
||||
ViewImageLoader.load(image, null, new UrlImageLoaderRequest(uri, 1000, 1000));
|
||||
edit.setText(getArguments().getString("existingDescription"));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
edit.requestFocus();
|
||||
view.postDelayed(()->getActivity().getSystemService(InputMethodManager.class).showSoftInput(edit, 0), 100);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
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 void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.saving, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ComposeMediaLayout extends ViewGroup{
|
||||
private static final int MAX_WIDTH_DP=400;
|
||||
private static final int GAP_DP=8;
|
||||
private static final float ASPECT_RATIO=0.5625f;
|
||||
|
||||
public ComposeMediaLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ComposeMediaLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ComposeMediaLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
int mode=MeasureSpec.getMode(widthMeasureSpec);
|
||||
@SuppressLint("SwitchIntDef")
|
||||
int width=switch(mode){
|
||||
case MeasureSpec.AT_MOST -> Math.min(V.dp(MAX_WIDTH_DP), MeasureSpec.getSize(widthMeasureSpec));
|
||||
case MeasureSpec.EXACTLY -> MeasureSpec.getSize(widthMeasureSpec);
|
||||
default -> throw new IllegalArgumentException("unsupported measure mode");
|
||||
};
|
||||
int height=Math.round(width*ASPECT_RATIO);
|
||||
setMeasuredDimension(width, height);
|
||||
|
||||
// We don't really need this, but some layouts will freak out if you don't measure them
|
||||
int childWidth, firstChildHeight, otherChildrenHeight=0;
|
||||
int gap=V.dp(GAP_DP);
|
||||
switch(getChildCount()){
|
||||
case 0 -> {
|
||||
return;
|
||||
}
|
||||
case 1 -> {
|
||||
childWidth=width;
|
||||
firstChildHeight=height;
|
||||
}
|
||||
case 2 -> {
|
||||
childWidth=(width-gap)/2;
|
||||
firstChildHeight=otherChildrenHeight=height;
|
||||
}
|
||||
case 3 -> {
|
||||
childWidth=(width-gap)/2;
|
||||
firstChildHeight=height;
|
||||
otherChildrenHeight=(height-gap)/2;
|
||||
}
|
||||
default -> {
|
||||
childWidth=(width-gap)/2;
|
||||
firstChildHeight=otherChildrenHeight=(height-gap)/2;
|
||||
}
|
||||
}
|
||||
for(int i=0;i<getChildCount();i++){
|
||||
getChildAt(i).measure(childWidth | MeasureSpec.EXACTLY, (i==0 ? firstChildHeight : otherChildrenHeight) | MeasureSpec.EXACTLY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b){
|
||||
int gap=V.dp(GAP_DP);
|
||||
int width=r-l;
|
||||
int height=b-t;
|
||||
int halfWidth=(width-gap)/2;
|
||||
int halfHeight=(height-gap)/2;
|
||||
switch(getChildCount()){
|
||||
case 0 -> {}
|
||||
case 1 -> getChildAt(0).layout(0, 0, width, height);
|
||||
case 2 -> {
|
||||
getChildAt(0).layout(0, 0, halfWidth, height);
|
||||
getChildAt(1).layout(halfWidth+gap, 0, width, height);
|
||||
}
|
||||
case 3 -> {
|
||||
getChildAt(0).layout(0, 0, halfWidth, height);
|
||||
getChildAt(1).layout(halfWidth+gap, 0, width, halfHeight);
|
||||
getChildAt(2).layout(halfWidth+gap, halfHeight+gap, width, height);
|
||||
}
|
||||
default -> {
|
||||
getChildAt(0).layout(0, 0, halfWidth, halfHeight);
|
||||
getChildAt(1).layout(halfWidth+gap, 0, width, halfHeight);
|
||||
getChildAt(2).layout(0, halfHeight+gap, halfWidth, height);
|
||||
getChildAt(3).layout(halfWidth+gap, halfHeight+gap, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user