mirror of
https://github.com/nuclearfog/Shitter.git
synced 2025-01-01 04:17:21 +01:00
integrated ZoomView, Tagger & LinkAndScrollMovement library into package, removed gradle dependencies
This commit is contained in:
parent
20718465b6
commit
64740ea1ba
@ -61,9 +61,6 @@ dependencies {
|
||||
//noinspection GradleDependency
|
||||
implementation 'com.squareup.picasso:picasso:2.8'
|
||||
implementation 'com.github.QuadFlask:colorpicker:0.0.15'
|
||||
implementation 'com.github.nuclearfog:ZoomView:1.0.4'
|
||||
implementation 'com.github.nuclearfog:Tagger:2.4'
|
||||
implementation 'com.github.nuclearfog:LinkAndScrollMovement:1.4.1'
|
||||
implementation 'com.github.kyleduo:SwitchButton:2.0.3-SNAPSHOT'
|
||||
implementation 'com.github.UnifiedPush:android-connector:2.1.1'
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
|
@ -10,7 +10,7 @@
|
||||
<body>
|
||||
<h3>Notices for libraries:</h3>
|
||||
<ul>
|
||||
<li>Shitter, ZoomView, Tagger, LinkAndScrollMovement</li>
|
||||
<li>Shitter</li>
|
||||
</ul>
|
||||
<pre>
|
||||
Copyright 2018 nuclearfog
|
||||
|
@ -0,0 +1,94 @@
|
||||
package org.nuclearfog.twidda.backend.utils;
|
||||
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* @author nuclearfog
|
||||
*/
|
||||
public class LinkAndScrollMovement extends ScrollingMovementMethod {
|
||||
|
||||
private static final LinkAndScrollMovement instance = new LinkAndScrollMovement();
|
||||
|
||||
/**
|
||||
* setup the x axis threshold to disable click events.
|
||||
*/
|
||||
private static final int THRESHOLD_WIDTH_DIVIDER = 6;
|
||||
|
||||
/**
|
||||
* setup the y axis threshold to disable click events.
|
||||
*/
|
||||
private static final int THRESHOLD_HEIGHT_DIVIDER = 3;
|
||||
|
||||
private int xScroll = 0;
|
||||
private int yScroll = 0;
|
||||
|
||||
/**
|
||||
*/
|
||||
private LinkAndScrollMovement() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
|
||||
switch(event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
lockParentScrolling(widget, true);
|
||||
xScroll = widget.getScrollX();
|
||||
yScroll = widget.getScrollY();
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
lockParentScrolling(widget, false);
|
||||
int deltaX = Math.abs(widget.getScrollX() - xScroll);
|
||||
int deltaY = Math.abs(widget.getScrollY() - yScroll);
|
||||
if (deltaY <= widget.getTextSize() / THRESHOLD_HEIGHT_DIVIDER && deltaX <= widget.getWidth() / THRESHOLD_WIDTH_DIVIDER) {
|
||||
int x = (int) event.getX();
|
||||
int y = (int) event.getY();
|
||||
x -= widget.getTotalPaddingLeft();
|
||||
y -= widget.getTotalPaddingTop();
|
||||
x += widget.getScrollX();
|
||||
y += widget.getScrollY();
|
||||
Layout layout = widget.getLayout();
|
||||
int line = layout.getLineForVertical(y);
|
||||
int off = layout.getOffsetForHorizontal(line, x);
|
||||
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
|
||||
if (link.length > 0) {
|
||||
link[0].onClick(widget);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return super.onTouchEvent(widget, buffer, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* lock parent view scrolling
|
||||
*
|
||||
* @param widget interacting TextView
|
||||
* @param lock true if parent views scrolling should be locked
|
||||
*/
|
||||
private void lockParentScrolling(TextView widget, boolean lock) {
|
||||
ViewParent parent = widget.getParent();
|
||||
int lineCount = widget.getLineCount();
|
||||
int maxLines = widget.getMaxLines();
|
||||
if ( parent != null && maxLines > 0 && lineCount > maxLines ) {
|
||||
parent.requestDisallowInterceptTouchEvent(lock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance of the movement method
|
||||
*
|
||||
* @return LinkAndScrollingMovementMethod object
|
||||
*/
|
||||
public static LinkAndScrollMovement getInstance() {
|
||||
return instance;
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
package org.nuclearfog.twidda.backend.utils;
|
||||
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.util.Patterns;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author nuclearfog
|
||||
*/
|
||||
public class Tagger {
|
||||
|
||||
/**
|
||||
* regex patterns used to get @usernames and #hashtags
|
||||
*/
|
||||
private static final Pattern[] PATTERNS = {
|
||||
Pattern.compile("@[^#\"“”‘’«»„"⹂‟`*'~,;‚‛:<>|^!/§%&()=?´°{}+\\-\\[\\]\\s]+"),
|
||||
Pattern.compile("#[^@#\"“”‘’«»„"⹂‟`*'~,;‚.‛:<>|^!/§%&()=?´°{}+\\-\\[\\]\\s]+")
|
||||
};
|
||||
|
||||
/**
|
||||
* default span type
|
||||
*/
|
||||
private static final int SPAN_TYPE = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE;
|
||||
|
||||
/**
|
||||
* maximum link url length before truncating
|
||||
*/
|
||||
private static final int MAX_LINK_LENGTH = 30;
|
||||
|
||||
/**
|
||||
* Make a spannable colored String with click listener
|
||||
*
|
||||
* @param text String that should be spannable
|
||||
* @param color Text Color
|
||||
* @param l click listener
|
||||
* @return Spannable String
|
||||
*/
|
||||
public static Spannable makeText(@Nullable String text, final int color, @NonNull final OnTagClickListener l) {
|
||||
SpannableStringBuilder spannable = new SpannableStringBuilder();
|
||||
/// Add '@' & '#' highlighting + listener
|
||||
if (text != null && text.length() > 0) {
|
||||
spannable.append(text);
|
||||
for (Pattern pattern : PATTERNS) {
|
||||
Matcher m = pattern.matcher(spannable);
|
||||
while (m.find()) {
|
||||
int end = m.end();
|
||||
int start = m.start();
|
||||
final String tag = spannable.subSequence(start, end).toString();
|
||||
spannable.setSpan(new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(@NonNull View widget) {
|
||||
l.onTagClick(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(@NonNull TextPaint ds) {
|
||||
ds.setColor(color);
|
||||
ds.setUnderlineText(false);
|
||||
}
|
||||
}, start, end, SPAN_TYPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
return spannable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a spannable colored String with click listener
|
||||
* http(s) links included
|
||||
*
|
||||
* @param text String that should be spannable
|
||||
* @param color Text Color
|
||||
* @param l click listener
|
||||
* @return Spannable String
|
||||
*/
|
||||
public static Spannable makeTextWithLinks(@Nullable String text, final int color, @NonNull final OnTagClickListener l) {
|
||||
SpannableStringBuilder spannable = new SpannableStringBuilder(makeText(text, color, l));
|
||||
// Add link highlight + listener
|
||||
if (spannable.length() > 0) {
|
||||
Stack<Integer> indexStack = new Stack<>();
|
||||
Matcher m = Patterns.WEB_URL.matcher(spannable.toString());
|
||||
while (m.find()) {
|
||||
indexStack.push(m.start());
|
||||
indexStack.push(m.end());
|
||||
}
|
||||
while (!indexStack.empty()) {
|
||||
int end = indexStack.pop();
|
||||
int start = indexStack.pop();
|
||||
final String link = spannable.subSequence(start, end).toString();
|
||||
if (link.startsWith("https://")) {
|
||||
spannable = spannable.delete(start, start + 8);
|
||||
end -= 8;
|
||||
} else if (link.startsWith("http://")) {
|
||||
spannable = spannable.delete(start, start + 7);
|
||||
end -= 7;
|
||||
}
|
||||
if (start + MAX_LINK_LENGTH < end) {
|
||||
spannable.replace(start + MAX_LINK_LENGTH, end, "...");
|
||||
end = start + MAX_LINK_LENGTH + 3;
|
||||
}
|
||||
spannable.setSpan(new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(@NonNull View widget) {
|
||||
l.onLinkClick(link);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(@NonNull TextPaint ds) {
|
||||
ds.setColor(color);
|
||||
ds.setUnderlineText(false);
|
||||
}
|
||||
}, start, end, SPAN_TYPE);
|
||||
}
|
||||
}
|
||||
return spannable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a spannable String without listener
|
||||
*
|
||||
* @param text String that should be spannable
|
||||
* @param color Text Color
|
||||
* @return Spannable String
|
||||
*/
|
||||
public static Spannable makeText(@Nullable String text, int color) {
|
||||
SpannableStringBuilder spannable = new SpannableStringBuilder();
|
||||
// Add '@' & '#' highlighting
|
||||
if (text != null && text.length() > 0) {
|
||||
spannable.append(text);
|
||||
for (Pattern pattern : PATTERNS) {
|
||||
Matcher m = pattern.matcher(spannable.toString());
|
||||
while (m.find()) {
|
||||
int end = m.end();
|
||||
int start = m.start();
|
||||
ForegroundColorSpan colorSpan = new ForegroundColorSpan(color);
|
||||
spannable.setSpan(colorSpan, start, end, SPAN_TYPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
return spannable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a spannable String without listener
|
||||
* http(s) links included will be shorted
|
||||
*
|
||||
* @param text String that should be spannable
|
||||
* @param color Text Color
|
||||
* @return Spannable String
|
||||
*/
|
||||
public static Spannable makeTextWithLinks(@Nullable String text, int color) {
|
||||
SpannableStringBuilder spannable = new SpannableStringBuilder(makeText(text, color));
|
||||
// Add link highlighting
|
||||
if (spannable.length() > 0) {
|
||||
Stack<Integer> indexStack = new Stack<>();
|
||||
Matcher m = Patterns.WEB_URL.matcher(spannable.toString());
|
||||
while (m.find()) {
|
||||
indexStack.push(m.start());
|
||||
indexStack.push(m.end());
|
||||
}
|
||||
while (!indexStack.empty()) {
|
||||
int end = indexStack.pop();
|
||||
int start = indexStack.pop();
|
||||
final String link = spannable.subSequence(start, end).toString();
|
||||
if (link.startsWith("https://")) {
|
||||
spannable = spannable.delete(start, start + 8);
|
||||
end -= 8;
|
||||
} else if (link.startsWith("http://")) {
|
||||
spannable = spannable.delete(start, start + 7);
|
||||
end -= 7;
|
||||
}
|
||||
if (start + MAX_LINK_LENGTH < end) {
|
||||
spannable.replace(start + MAX_LINK_LENGTH, end, "...");
|
||||
end = start + MAX_LINK_LENGTH + 3;
|
||||
}
|
||||
ForegroundColorSpan colorSpan = new ForegroundColorSpan(color);
|
||||
spannable.setSpan(colorSpan, start, end, SPAN_TYPE);
|
||||
}
|
||||
}
|
||||
return spannable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for clickable spans
|
||||
*/
|
||||
public interface OnTagClickListener {
|
||||
/**
|
||||
* Called when user clicks on a tag
|
||||
*
|
||||
* @param tag Tag string (starting with '@', '#')
|
||||
*/
|
||||
void onTagClick(String tag);
|
||||
|
||||
/**
|
||||
* Called when user clicks on link
|
||||
*
|
||||
* @param link http(s) link
|
||||
*/
|
||||
void onLinkClick(String link);
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog.DescriptionCallback;
|
||||
import org.nuclearfog.twidda.ui.dialogs.MetaDialog;
|
||||
import org.nuclearfog.twidda.ui.views.AnimatedImageView;
|
||||
import org.nuclearfog.twidda.ui.views.DescriptionView;
|
||||
import org.nuclearfog.zoomview.ZoomView;
|
||||
import org.nuclearfog.twidda.ui.views.ZoomView;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
|
@ -31,9 +31,6 @@ import com.squareup.picasso.Callback;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Transformation;
|
||||
|
||||
import org.nuclearfog.tag.Tagger;
|
||||
import org.nuclearfog.tag.Tagger.OnTagClickListener;
|
||||
import org.nuclearfog.textviewtool.LinkAndScrollMovement;
|
||||
import org.nuclearfog.twidda.R;
|
||||
import org.nuclearfog.twidda.backend.api.ConnectionException;
|
||||
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
|
||||
@ -45,8 +42,11 @@ import org.nuclearfog.twidda.backend.image.PicassoBuilder;
|
||||
import org.nuclearfog.twidda.backend.utils.AppStyles;
|
||||
import org.nuclearfog.twidda.backend.utils.EmojiUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.ErrorUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.LinkAndScrollMovement;
|
||||
import org.nuclearfog.twidda.backend.utils.LinkUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.StringUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.Tagger;
|
||||
import org.nuclearfog.twidda.backend.utils.Tagger.OnTagClickListener;
|
||||
import org.nuclearfog.twidda.backend.utils.ToolbarUpdater;
|
||||
import org.nuclearfog.twidda.config.GlobalSettings;
|
||||
import org.nuclearfog.twidda.model.Relation;
|
||||
|
@ -38,9 +38,6 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Transformation;
|
||||
|
||||
import org.nuclearfog.tag.Tagger;
|
||||
import org.nuclearfog.tag.Tagger.OnTagClickListener;
|
||||
import org.nuclearfog.textviewtool.LinkAndScrollMovement;
|
||||
import org.nuclearfog.twidda.R;
|
||||
import org.nuclearfog.twidda.backend.api.ConnectionException;
|
||||
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
|
||||
@ -53,8 +50,11 @@ import org.nuclearfog.twidda.backend.image.PicassoBuilder;
|
||||
import org.nuclearfog.twidda.backend.utils.AppStyles;
|
||||
import org.nuclearfog.twidda.backend.utils.EmojiUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.ErrorUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.LinkAndScrollMovement;
|
||||
import org.nuclearfog.twidda.backend.utils.LinkUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.StringUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.Tagger;
|
||||
import org.nuclearfog.twidda.backend.utils.Tagger.OnTagClickListener;
|
||||
import org.nuclearfog.twidda.config.GlobalSettings;
|
||||
import org.nuclearfog.twidda.model.Card;
|
||||
import org.nuclearfog.twidda.model.Location;
|
||||
|
@ -5,7 +5,7 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView.Adapter;
|
||||
|
||||
import org.nuclearfog.tag.Tagger.OnTagClickListener;
|
||||
import org.nuclearfog.twidda.backend.utils.Tagger.OnTagClickListener;
|
||||
import org.nuclearfog.twidda.model.lists.Fields;
|
||||
import org.nuclearfog.twidda.ui.adapter.recyclerview.holder.FieldHolder;
|
||||
|
||||
|
@ -20,15 +20,15 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Transformation;
|
||||
|
||||
import org.nuclearfog.tag.Tagger;
|
||||
import org.nuclearfog.textviewtool.LinkAndScrollMovement;
|
||||
import org.nuclearfog.twidda.R;
|
||||
import org.nuclearfog.twidda.backend.async.AsyncExecutor;
|
||||
import org.nuclearfog.twidda.backend.async.TextEmojiLoader;
|
||||
import org.nuclearfog.twidda.backend.image.PicassoBuilder;
|
||||
import org.nuclearfog.twidda.backend.utils.AppStyles;
|
||||
import org.nuclearfog.twidda.backend.utils.EmojiUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.LinkAndScrollMovement;
|
||||
import org.nuclearfog.twidda.backend.utils.StringUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.Tagger;
|
||||
import org.nuclearfog.twidda.config.GlobalSettings;
|
||||
import org.nuclearfog.twidda.model.EditedStatus;
|
||||
import org.nuclearfog.twidda.model.User;
|
||||
|
@ -10,11 +10,11 @@ import android.widget.TextView;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||
|
||||
import org.nuclearfog.tag.Tagger;
|
||||
import org.nuclearfog.tag.Tagger.OnTagClickListener;
|
||||
import org.nuclearfog.twidda.R;
|
||||
import org.nuclearfog.twidda.backend.utils.AppStyles;
|
||||
import org.nuclearfog.twidda.backend.utils.StringUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.Tagger;
|
||||
import org.nuclearfog.twidda.backend.utils.Tagger.OnTagClickListener;
|
||||
import org.nuclearfog.twidda.config.GlobalSettings;
|
||||
import org.nuclearfog.twidda.model.User.Field;
|
||||
|
||||
|
@ -12,10 +12,10 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||
|
||||
import org.nuclearfog.tag.Tagger;
|
||||
import org.nuclearfog.textviewtool.LinkAndScrollMovement;
|
||||
import org.nuclearfog.twidda.R;
|
||||
import org.nuclearfog.twidda.backend.utils.AppStyles;
|
||||
import org.nuclearfog.twidda.backend.utils.LinkAndScrollMovement;
|
||||
import org.nuclearfog.twidda.backend.utils.Tagger;
|
||||
import org.nuclearfog.twidda.config.GlobalSettings;
|
||||
import org.nuclearfog.twidda.model.ScheduledStatus;
|
||||
import org.nuclearfog.twidda.model.Status;
|
||||
|
@ -23,7 +23,6 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Transformation;
|
||||
|
||||
import org.nuclearfog.tag.Tagger;
|
||||
import org.nuclearfog.twidda.R;
|
||||
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
|
||||
import org.nuclearfog.twidda.backend.async.TextEmojiLoader;
|
||||
@ -33,6 +32,7 @@ import org.nuclearfog.twidda.backend.image.PicassoBuilder;
|
||||
import org.nuclearfog.twidda.backend.utils.AppStyles;
|
||||
import org.nuclearfog.twidda.backend.utils.EmojiUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.StringUtils;
|
||||
import org.nuclearfog.twidda.backend.utils.Tagger;
|
||||
import org.nuclearfog.twidda.config.GlobalSettings;
|
||||
import org.nuclearfog.twidda.model.Notification;
|
||||
import org.nuclearfog.twidda.model.Status;
|
||||
|
219
app/src/main/java/org/nuclearfog/twidda/ui/views/ZoomView.java
Normal file
219
app/src/main/java/org/nuclearfog/twidda/ui/views/ZoomView.java
Normal file
@ -0,0 +1,219 @@
|
||||
package org.nuclearfog.twidda.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.RemoteViews.RemoteView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
import static android.view.MotionEvent.*;
|
||||
|
||||
import org.nuclearfog.twidda.R;
|
||||
|
||||
/**
|
||||
* Zoomable image view
|
||||
*
|
||||
* @author nuclearfog
|
||||
*/
|
||||
@RemoteView
|
||||
public class ZoomView extends AppCompatImageView {
|
||||
|
||||
// Default values
|
||||
private static final float DEF_MAX_ZOOM_IN = 3.0f;
|
||||
private static final float DEF_MAX_ZOOM_OUT = 0.5f;
|
||||
private static final boolean DEF_ENABLE_MOVE = true;
|
||||
private static final ScaleType DEF_SCALE_TYPE = ScaleType.FIT_CENTER;
|
||||
|
||||
// Layout Attributes
|
||||
private float max_zoom_in = DEF_MAX_ZOOM_IN;
|
||||
private float max_zoom_out = DEF_MAX_ZOOM_OUT;
|
||||
private boolean enableMove = DEF_ENABLE_MOVE;
|
||||
private ScaleType scaleType = DEF_SCALE_TYPE;
|
||||
|
||||
// intern flags
|
||||
private final PointF pos = new PointF(0.0f, 0.0f);
|
||||
private final PointF dist = new PointF(0.0f, 0.0f);
|
||||
private boolean moveLock = false;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public ZoomView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public ZoomView(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public ZoomView(Context context, @Nullable AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
scaleType = getScaleType();
|
||||
if (attrs != null) {
|
||||
TypedArray attrArray = context.obtainStyledAttributes(attrs, R.styleable.ZoomView);
|
||||
setMaxZoomIn(attrArray.getFloat(R.styleable.ZoomView_max_zoom_in, DEF_MAX_ZOOM_IN));
|
||||
setMaxZoomOut(attrArray.getFloat(R.styleable.ZoomView_max_zoom_out, DEF_MAX_ZOOM_OUT));
|
||||
setMovable(attrArray.getBoolean(R.styleable.ZoomView_enable_move, DEF_ENABLE_MOVE));
|
||||
attrArray.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean performClick() {
|
||||
return super.performClick();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (getScaleType() != ScaleType.MATRIX)
|
||||
setScaleType(ScaleType.MATRIX);
|
||||
if (event.getPointerCount() == 1) {
|
||||
|
||||
switch (event.getAction()) {
|
||||
case ACTION_UP:
|
||||
pos.set(event.getX(), event.getY());
|
||||
moveLock = false;
|
||||
break;
|
||||
|
||||
case ACTION_DOWN:
|
||||
pos.set(event.getX(), event.getY());
|
||||
break;
|
||||
|
||||
case ACTION_MOVE:
|
||||
if (moveLock || !enableMove)
|
||||
return super.performClick();
|
||||
float posX = event.getX() - pos.x;
|
||||
float posY = event.getY() - pos.y;
|
||||
pos.set(event.getX(), event.getY());
|
||||
Matrix m = new Matrix(getImageMatrix());
|
||||
m.postTranslate(posX, posY);
|
||||
apply(m);
|
||||
break;
|
||||
}
|
||||
} else if (event.getPointerCount() == 2) {
|
||||
float distX, distY, scale;
|
||||
switch (event.getActionMasked()) {
|
||||
case ACTION_POINTER_UP:
|
||||
case ACTION_POINTER_DOWN:
|
||||
distX = event.getX(0) - event.getX(1);
|
||||
distY = event.getY(0) - event.getY(1);
|
||||
dist.set(distX, distY); // Distance vector
|
||||
moveLock = true;
|
||||
break;
|
||||
|
||||
case ACTION_MOVE:
|
||||
distX = event.getX(0) - event.getX(1);
|
||||
distY = event.getY(0) - event.getY(1);
|
||||
PointF current = new PointF(distX, distY);
|
||||
scale = current.length() / dist.length();
|
||||
Matrix m = new Matrix(getImageMatrix());
|
||||
m.postScale(scale, scale, getWidth() / 2.0f, getHeight() / 2.0f);
|
||||
dist.set(distX, distY);
|
||||
apply(m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset Image position/zoom to default
|
||||
*/
|
||||
public void reset() {
|
||||
setScaleType(scaleType);
|
||||
}
|
||||
|
||||
/**
|
||||
* set Image movable
|
||||
*
|
||||
* @param enableMove set image movable
|
||||
*/
|
||||
public void setMovable(boolean enableMove) {
|
||||
this.enableMove = enableMove;
|
||||
}
|
||||
|
||||
/**
|
||||
* set maximum zoom in
|
||||
*
|
||||
* @param max_zoom_in maximum zoom value
|
||||
*/
|
||||
public void setMaxZoomIn(float max_zoom_in) {
|
||||
if (max_zoom_in < 1.0f)
|
||||
throw new AssertionError("value should be more 1.0!");
|
||||
this.max_zoom_in = max_zoom_in;
|
||||
}
|
||||
|
||||
/**
|
||||
* set maximum zoom in
|
||||
*
|
||||
* @param max_zoom_out maximum zoom value
|
||||
*/
|
||||
public void setMaxZoomOut(float max_zoom_out) {
|
||||
if (max_zoom_out > 1.0f)
|
||||
throw new AssertionError("value should be less 1.0!");
|
||||
this.max_zoom_out = max_zoom_out;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private void apply(Matrix m) {
|
||||
Drawable d = getDrawable();
|
||||
if (d == null) return;
|
||||
|
||||
float[] val = new float[9];
|
||||
m.getValues(val);
|
||||
float scale = (val[Matrix.MSCALE_X] + val[Matrix.MSCALE_Y]) / 2; // Scale factor
|
||||
float width = d.getIntrinsicWidth() * scale; // image width
|
||||
float height = d.getIntrinsicHeight() * scale; // image height
|
||||
float leftBorder = val[Matrix.MTRANS_X]; // distance to left border
|
||||
float rightBorder = -(val[Matrix.MTRANS_X] + width - getWidth()); // distance to right border
|
||||
float bottomBorder = val[Matrix.MTRANS_Y]; // distance to bottom border
|
||||
float topBorder = -(val[Matrix.MTRANS_Y] + height - getHeight()); // distance to top border
|
||||
|
||||
if (width > getWidth()) { // is image width bigger than screen width?
|
||||
if (rightBorder > 0) // is image on the right border?
|
||||
m.postTranslate(rightBorder, 0); // clamp to right border
|
||||
else if (leftBorder > 0)
|
||||
m.postTranslate(-leftBorder, 0); // clamp to left order
|
||||
} else if (leftBorder < 0 ^ rightBorder < 0) { // does image clash with one border?
|
||||
if (rightBorder < 0)
|
||||
m.postTranslate(rightBorder, 0); // clamp to right border
|
||||
else
|
||||
m.postTranslate(-leftBorder, 0); // clamp to left border
|
||||
}
|
||||
if (height > getHeight()) { // is image height bigger than screen height?
|
||||
if (bottomBorder > 0) // is image on the bottom border?
|
||||
m.postTranslate(0, -bottomBorder); // clamp to bottom border
|
||||
else if (topBorder > 0) // is image on the top border?
|
||||
m.postTranslate(0, topBorder); // clamp to top border
|
||||
} else if (topBorder < 0 ^ bottomBorder < 0) { // does image clash with one border?
|
||||
if (bottomBorder < 0)
|
||||
m.postTranslate(0, -bottomBorder); // clamp to bottom border
|
||||
else
|
||||
m.postTranslate(0, topBorder); // clamp to top border
|
||||
}
|
||||
if (scale > max_zoom_in) { // scale limit exceeded?
|
||||
float undoScale = max_zoom_in / scale; // undo scale setting
|
||||
m.postScale(undoScale, undoScale, getWidth() / 2.0f, getHeight() / 2.0f);
|
||||
} else if (scale < max_zoom_out) { // scale limit exceeded?
|
||||
float undoScale = max_zoom_out / scale; // undo scale setting
|
||||
m.postScale(undoScale, undoScale, getWidth() / 2.0f, getHeight() / 2.0f);
|
||||
}
|
||||
setImageMatrix(m); // set Image matrix
|
||||
}
|
||||
}
|
@ -9,4 +9,10 @@
|
||||
<attr name="viewpager" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ZoomView">
|
||||
<attr name="max_zoom_in" format="float" />
|
||||
<attr name="max_zoom_out" format="float" />
|
||||
<attr name="enable_move" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user