How to make slide to unlock button in android - android

Hi I want a button that should work as 'slide to unlock' button of IOS
in short I want a button that has no click effect but can slide left to right while drag and on drag completion it should considered click.
please suggest me any sample code if possible.
Thanks!

First of all I'd like to thank #matthias for his answer.
I have used the following seek bar with some customization:
<SeekBar
android:id="#+id/myseek"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:clickable="false"
android:max="100"
android:progressDrawable="#android:color/transparent"
android:thumb="#drawable/ic_launcher" />
and in java code
sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (seekBar.getProgress() > 95) {
} else {
seekBar.setThumb(getResources().getDrawable(R.drawable.ic_launcher));
}
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
if(progress>95){
seekBar.setThumb(getResources().getDrawable(R.drawable.load_img1));
}
}
});

I started out with the example that Jignesh Ansodariya posted, but as Aerrow points out, the user can click anywhere on the SeekBar to unlock. That makes it quite unusable, since the point with having a slide button is that accidental clicks should be ignored. My solution was to create a subclass of SeekBar, like this:
public class SlideButton extends SeekBar {
private Drawable thumb;
private SlideButtonListener listener;
public SlideButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void setThumb(Drawable thumb) {
super.setThumb(thumb);
this.thumb = thumb;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (thumb.getBounds().contains((int) event.getX(), (int) event.getY())) {
super.onTouchEvent(event);
} else
return false;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (getProgress() > 70)
handleSlide();
setProgress(0);
} else
super.onTouchEvent(event);
return true;
}
private void handleSlide() {
listener.handleSlide();
}
public void setSlideButtonListener(SlideButtonListener listener) {
this.listener = listener;
}
}
public interface SlideButtonListener {
public void handleSlide();
}
XML:
<package.SlideButton
android:id="#+id/unlockButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="false"
android:max="100"
android:progressDrawable="#android:color/transparent"
android:thumb="#drawable/button_lock" >
</package.SlideButton>
And finally the code inside my Activity:
((SlideButton) findViewById(R.id.unlockButton)).setSlideButtonListener(new SlideButtonListener() {
#Override
public void handleSlide() {
unlockScreen();
}
});

There are some good libraries to do the trick for you.
If using a library to perform this is not an issue for you, then consider trying this one:
https://github.com/cortinico/slidetoact
Happy coding..!! :)

Android provides the Switch widget that is similar to slide to unlock. However, you will have to customize it a little, e.g. disable change on click.

You can use this library to quickly and easy customize your unlock.
https://github.com/cheekiat/SlideToUnlock
Use this code on xml
<cheekiat.slideview.SlideView
android:id="#+id/slide_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:slideBackground="#drawable/orangesquarebutton"
app:slideSrc="#drawable/slide_image"
app:slideText="Slide to unlock"
app:slideTextColor="#ffffff"
app:slideTextSize="10dp" />
Slide to unlock screenshort

For some reason, I couldn't disable the touch action so accidental taps are still possible
I came up with this solution, it basically won't allow changing the progress by more than 10 values per change event
int unlockLastSeekVal = 0;
// there are skips btwn changes, use value greater than 1
// 10 is great for seekbars with 100 values
int unlockSeekSensitivity = 10;
// final stage to "unlock"; 0.9 => 90%
Double unlockFinalStage = 0.9;
//...........
unlock.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser){
if (Math.abs(unlockLastSeekVal - progress) > unlockSeekSensitivity){
// too much delta, revert to last value
seekBar.setProgress(unlockLastSeekVal);
}else{
unlockLastSeekVal = progress;
}
}
}
public void onStartTrackingTouch(SeekBar seekBar) {
}
public void onStopTrackingTouch(SeekBar seekBar) {
if (seekBar.getProgress() > seekBar.getMax() * unlockFinalStage){
DoYourThing();
}
unlockLastSeekVal = 0;
seekBar.setProgress(0);
}
});

Starting from Oskar's answer (thanks for your contribute) i create a simple example project to manage Slide Button (horizontal and vertical) :
https://github.com/rcaboni/AndroidSlideButton
For a screen shot : https://raw.githubusercontent.com/rcaboni/AndroidSlideButton/master/screenshot.jpg
This is the main method :
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
if (orientation == ORIENTATION_HORIZONTAL) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
int x= (int) event.getX();
int y= (int) event.getY();
if (thumb.getBounds().contains((int) event.getX(), (int) event.getY())) {
super.onTouchEvent(event);
} else
return false;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (getProgress() > 70)
handleSlide();
setProgress(0);
} else
super.onTouchEvent(event);
}else{
int i=0;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (event.getAction() == MotionEvent.ACTION_DOWN) {
int x= (int) event.getX();
int y= (int) event.getY();
if (!thumb.getBounds().contains((int) event.getY(), (int) event.getX())) {
return false;
}
}
case MotionEvent.ACTION_MOVE:
i=getMax() - (int) (getMax() * event.getY() / getHeight());
setProgress(100 - i);
onSizeChanged(getWidth(), getHeight(), 0, 0);
break;
case MotionEvent.ACTION_UP:
i=getMax() - (int) (getMax() * event.getY() / getHeight());
if (i < 30) {
handleSlide();
}
setProgress(0);
onSizeChanged(getWidth(), getHeight(), 0, 0);
break;
case MotionEvent.ACTION_CANCEL:
break;
}
}
return true;
}
XML for vertical button :
<RelativeLayout
android:layout_width="75dp"
android:layout_height="130dp"
android:background="#drawable/slide_background_green"
android:id="#+id/lSlideButtonV"
android:layout_below="#+id/lSlideButton"
android:layout_marginTop="50dp">
<TextView
android:layout_width="20dp"
android:layout_height="match_parent"
android:text="SOS"
android:id="#+id/tvSlideActionV"
android:gravity="center|bottom"
android:layout_alignParentRight="false"
android:layout_alignParentEnd="false"
android:layout_alignParentLeft="false"
android:layout_alignParentStart="false"
android:textSize="20dp"
android:textColor="#android:color/white"
android:layout_alignParentTop="false"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="false"
android:layout_marginBottom="15dp" />
<it.aldea.android.widget.SlideButton
android:id="#+id/unlockButtonV"
android:layout_width="match_parent"
android:layout_height="150dp"
android:clickable="false"
android:max="100"
slideButton:orientation="vertical"
android:progressDrawable="#android:color/transparent"
android:thumb="#drawable/slide_track_red"
android:indeterminate="false"
android:layout_marginRight="5dp"
android:layout_marginTop="20dp"
android:layout_centerInParent="true"
android:layout_marginBottom="10dp"
android:thumbOffset="-2dp">
</it.aldea.android.widget.SlideButton>
</RelativeLayout>
It's not a real complete widget because is composed from two view (TextView and SlideButton) into a Layout, but it's a easy configurable solution for Slide Button with text inside.
I hope this is useful for someone.

It maybe very late but I have created a small library for this very purpose. It allows you to swipe and customise the behaviour of your button from xml. Slide Button
Basically it involves overriding of the onTouch() event and making changes according to the coordinates that are received. Its a simple thing after that to set the background as you want and customise the text.

You can rebuild the normal SeekBar to do what you want:
With:
Starting point (20)
Crossing line (90)
And auto reset.
seekBar.setOnSeekBarChangeListener(
new SeekBar.OnSeekBarChangeListener() {
Integer point = 0;
Integer startPoint = 0;
boolean started = true;
#Override
public void onProgressChanged(SeekBar seekBar, int i, boolean wasUserInput) {
point = i;
if (started && i > 0 && wasUserInput) {
startPoint = new Integer(i);
started = false;
}
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (point > 90 && startPoint < 20) { // slided to the right correctly.
// TODO::
} else { // reset.
resetSeekBar(seekBar, point);
}
startPoint = 0;
started = true;
}
});
And:
/**
* Resetting the seekbar, on point at a time.
* #param seekBar Reference to the seekbar made smaller.
* #param oldPoint The point where the dot is atm.
*/
private void resetSeekBar(final SeekBar seekBar, final int oldPoint) {
if (oldPoint > 0) {
final int newPoint = oldPoint -1;
seekBar.setProgress(newPoint);
timer.schedule(new TimerTask() {
final SeekBar seekBar = seekBarBid;
#Override
public void run() {
resetSeekBar(seekBar, newPoint);
}
}, 3);
} else {
seekBar.setProgress(oldPoint);
}
}

I Hope below Ans is work,
public class UnlockSliderView extends FrameLayout {
#BindView(R2.id.tv_unlock_slider)
TextView tvUnlockSlider;
#BindView(R2.id.iv_circle_slide)
ImageView ivCircleSlide;
private View parentCircle;
private float xOrigin = 0;
private float xOriginCircle = 0;
private boolean circleTouched = false;
public UnlockSliderView(Context context) {
super(context);
init();
}
public UnlockSliderView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public UnlockSliderView(Context context, AttributeSet attr, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
inflate(getContext(), R.layout.layout_unlock_slider_view, this);
ButterKnife.bind(this);
parentCircle = (View) ivCircleSlide.getParent();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
eventActionDown(event);
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
eventActionMove(event);
} else if (event.getAction() == MotionEvent.ACTION_UP) {
unlockFinish();
}
return true;
}
private void eventActionDown(MotionEvent event) {
if ((event.getX() >= ivCircleSlide.getX() && event.getX() <= ivCircleSlide.getX() + ivCircleSlide.getWidth())
&& (event.getY() >= ivCircleSlide.getY() && event.getY() <= ivCircleSlide.getY() + ivCircleSlide.getHeight())) {
xOrigin = event.getX();
xOriginCircle = ivCircleSlide.getX();
circleTouched = true;
} else {
circleTouched = false;
}
}
private void eventActionMove(MotionEvent event) {
if (circleTouched) {
float newXCircle = xOriginCircle + (event.getX() - xOrigin);
newXCircle = (newXCircle < xOriginCircle) ? xOriginCircle : newXCircle;
newXCircle = (newXCircle > parentCircle.getWidth() - ivCircleSlide.getWidth() - xOriginCircle) ? parentCircle.getWidth() - ivCircleSlide.getWidth() - xOriginCircle : newXCircle;
float alpha = 1 - ((newXCircle - xOriginCircle) / (parentCircle.getWidth() - ivCircleSlide.getWidth() - (xOriginCircle * 2)));
tvUnlockSlider.setAlpha(alpha);
ivCircleSlide.setX(newXCircle);
if (newXCircle == parentCircle.getWidth() - ivCircleSlide.getWidth() - xOriginCircle) {
unlockFinish();
if (mListener != null) mListener.onUnlock();
}
}
}
private void unlockFinish() {
if (circleTouched) {
ivCircleSlide.animate().x(xOriginCircle).setDuration(400).start();
tvUnlockSlider.animate().alpha(1).setDuration(400).start();
circleTouched = false;
}
}
and the xml is,
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="#dimen/unlock_slider_height"
android:layout_margin="#dimen/default_padding"
android:background="#drawable/btn_slider_back"
android:gravity="center"
android:orientation="vertical"
android:padding="4dp">
<TextView
android:id="#+id/tv_unlock_slider"
style="#style/prelogin_slider"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginLeft="#dimen/unlock_slider_handle_size"
android:layout_marginStart="#dimen/unlock_slider_handle_size"
android:text="#string/logon_to_mobile_banking" />
<ImageView
android:id="#+id/iv_circle_slide"
android:layout_width="#dimen/unlock_slider_handle_size"
android:layout_height="#dimen/unlock_slider_handle_size"
android:scaleType="fitCenter"
android:src="#drawable/btn_slider_handle"
tools:ignore="ContentDescription" />
</FrameLayout>

Thanks, #Oskar Lundgren for the answer I have updated some of the things and if anybody is looking to do the same in kotlin. Here it is
SlideToConfirm
class SlideToConfirm : SeekBar, SeekBar.OnSeekBarChangeListener {
private lateinit var listener: SlideButtonListener
// To prevent the thumb to going out of track
private val maxProgress = 91
private val minProgress = 9
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
init()
}
fun init() {
setDrawables()
setProperties()
setOnSeekBarChangeListener(this)
}
private fun setDrawables() {
thumb = ContextCompat.getDrawable(context, R.drawable.slider_thumb)
progressDrawable = ContextCompat.getDrawable(context, R.drawable.slider_progress_drawable)
}
private fun setProperties() {
isClickable = false
splitTrack = false
setPadding(0, 0, 0, 0)
progress = minProgress
}
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (progress < minProgress) {
this.progress = minProgress
}
if (progress > maxProgress) {
this.progress = maxProgress
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
#SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event!!.action == MotionEvent.ACTION_DOWN) {
if (thumb.bounds.contains(event.x.toInt(), event.y.toInt())) {
super.onTouchEvent(event)
} else
return false
} else if (event.action == MotionEvent.ACTION_UP) {
if (progress > 70) {
handleSlide()
progress = maxProgress
} else {
progress = minProgress
}
} else {
super.onTouchEvent(event)
}
return true
}
fun setOnSlideListener(listener: SlideButtonListener) {
this.listener = listener
}
private fun handleSlide() {
listener.handleSlide()
}
interface SlideButtonListener {
fun handleSlide()
}
}
Thumb
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="#color/cyan" />
<corners android:radius="8dp" />
<size
android:width="50dp"
android:height="50dp" />
</shape>
</item>
<item android:drawable="#drawable/ic_arrow_forward_white" />
</layer-list>
Progress Track
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="35dp" />
<size
android:width="100dp"
android:height="50dp" />
<stroke
android:width="1dp"
android:color="#color/errorRed" />
<solid android:color="#android:color/white" />
</shape>

Related

HideBottomViewOnScrollBehavior not working on recyclerview item expand/collapse

I am trying to hide text view on scroll down and show on scroll up it's working fine if I have an item like 10 or 15 but it's not working the same if I have less item
in recyclerview, I have expanded/collapse functionality so it's not the same sometimes
textview not hiding/visible some times I don't understand I added this line to my view which I want to hide/show on scroll
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
XML
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/lnMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#mipmap/bg"
tools:context=".tab.history.view.HistoryFragment">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/mAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">
<RelativeLayout
android:id="#+id/lnActionBar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="#color/colorPrimary">
<TextView
android:id="#+id/tvtitle"
style="#style/fontMedium"
android:layout_width="wrap_content"
android:layout_height="?android:attr/actionBarSize"
android:layout_centerHorizontal="true"
android:gravity="center_vertical"
android:text="#string/beacon"
android:textColor="#color/white"
android:textSize="#dimen/header_font_size" />
</RelativeLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvBeacon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:paddingBottom="#dimen/_40sdp"
android:scrollbars="none"
android:visibility="visible"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="10"
tools:listitem="#layout/raw_beacon" />
<TextView
android:id="#+id/btnBack"
style="#style/fontBold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="#dimen/_10sdp"
android:layout_marginBottom="#dimen/_20sdp"
android:background="#drawable/background_button_yellow_20dp"
android:contentDescription="#string/back_button"
android:drawableStart="#drawable/ic_back"
android:layout_gravity="bottom|center_horizontal"
android:drawablePadding="#dimen/_5sdp"
android:gravity="center"
android:padding="#dimen/_10sdp"
android:text="#string/back_to_search"
android:textColor="#color/white"
android:textSize="#dimen/button_font_size"
android:visibility="#{!isScanning ? View.VISIBLE: View.GONE}"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
this is what I did so far but it's not working every time
Note:- Please not I have a recyclerview item which expands onclick so scroll and textview must behave according to that
Any help would be highly appriciated
i don't know why HideBottomViewOnScrollBehavior is not working for you
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
may be it's beacause you have a expand/collapse functionality since you have only recyclerview only in screen so you can also perform this task by adding Custom ScrollListener
MyRecyclerScroll class
public abstract class MyRecyclerScroll extends RecyclerView.OnScrollListener {
private static final float HIDE_THRESHOLD = 100;
private static final float SHOW_THRESHOLD = 50;
int scrollDist = 0;
private boolean isVisible = true;
// We dont use this method because its action is called per pixel value change
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// Check scrolled distance against the minimum
if (isVisible && scrollDist > HIDE_THRESHOLD) {
// Hide fab & reset scrollDist
hide();
scrollDist = 0;
isVisible = false;
}
// -MINIMUM because scrolling up gives - dy values
else if (!isVisible && scrollDist < -SHOW_THRESHOLD) {
// Show fab & reset scrollDist
show();
scrollDist = 0;
isVisible = true;
}
// Whether we scroll up or down, calculate scroll distance
if ((isVisible && dy > 0) || (!isVisible && dy < 0)) {
scrollDist += dy;
}
}
public abstract void show();
public abstract void hide();
}
Activity/Fragment
binding.rvBeacon.addOnScrollListener(object : MyRecyclerScroll() {
override fun show() {
binding.btnBack.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f)).start()
}
override fun hide() {
binding.btnBack.animate().translationY(binding.btnBack.getHeight() + 60f)
.setInterpolator(AccelerateInterpolator(2f)).start()
}
})
you can change animation, delay and margin according to your requirement
for more detail refer to this blog
Note: it will not work if your recyclerview inside scrollview
how about make custom behavior?
for example.
public class QuickReturnFooterBehavior extends CoordinatorLayout.Behavior<View> {
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private static final long ANIMATION_DURATION = 200;
private int dyDirectionSum;
private boolean isShowing;
private boolean isHiding;
private boolean isNeedOption = true;
public boolean isNeedOption() {
return isNeedOption;
}
public void setNeedOption(boolean needOption) {
isNeedOption = needOption;
}
public QuickReturnFooterBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onStartNestedScroll(#NonNull CoordinatorLayout coordinatorLayout, #NonNull View child, #NonNull View directTargetChild, #NonNull View target, int axes, int type) {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
#Override
public void onNestedPreScroll(#NonNull CoordinatorLayout coordinatorLayout, #NonNull View child, #NonNull View target, int dx, int dy, #NonNull int[] consumed, int type) {
// scroll chhange up and down
if (isNeedOption) {
showView(child);
} else {
if (dy > 0 && dyDirectionSum < 0
|| dy < 0 && dyDirectionSum > 0) {
child.animate().cancel();
dyDirectionSum = 0;
}
dyDirectionSum += dy;
if (dyDirectionSum > child.getHeight()) {
hideView(child);
} else if (dyDirectionSum < -child.getHeight()) {
showView(child);
}
}
}
private void hideView(final View view) {
if (isHiding || view.getVisibility() != View.VISIBLE) {
return;
}
ViewPropertyAnimator animator = view.animate()
.translationY(view.getHeight())
.setInterpolator(INTERPOLATOR)
.setDuration(ANIMATION_DURATION);
animator.setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
isHiding = true;
}
#Override
public void onAnimationEnd(Animator animator) {
isHiding = false;
view.setVisibility(View.INVISIBLE);
}
#Override
public void onAnimationCancel(Animator animator) {
// show when cancle
isHiding = false;
showView(view);
}
#Override
public void onAnimationRepeat(Animator animator) {
// no-op
}
});
animator.start();
}
private void showView(final View view) {
if (isShowing || view.getVisibility() == View.VISIBLE) {
return;
}
ViewPropertyAnimator animator = view.animate()
.translationY(0)
.setInterpolator(INTERPOLATOR)
.setDuration(ANIMATION_DURATION);
animator.setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
isShowing = true;
view.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd(Animator animator) {
isShowing = false;
}
#Override
public void onAnimationCancel(Animator animator) {
// show when cancle
isShowing = false;
hideView(view);
}
#Override
public void onAnimationRepeat(Animator animator) {
// no-op
}
});
animator.start();
}
}
UPDATE : for check can scroll in behavior
child.canScrollVertically(1) // "Top of list"
child.canScrollVertically(-1) // "End of list"
**UPDATE : add setter and getter **
private boolean isNeedOption = true;

Layout onClick event does not work if it has Webview as a child

My Issue is that I have a LinearLayout that I am inflating at runtime to a LinearLayout inside ScrollView.
This is the main_activity.xml
<ScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/controlLayoutCV"
android:layout_alignParentStart="true"
android:layout_below="#+id/toolLayoutCV">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="#dimen/dp5"
android:paddingLeft="#dimen/dp7"
android:paddingRight="#dimen/dp7"
android:paddingTop="#dimen/dp5">
<android.support.v7.widget.CardView
android:id="#+id/questionQuizCV"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="#dimen/dp2">
<com.emedicoz.app.CustomViews.JustifiedTextView
android:id="#+id/questionQuizTV"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="#dimen/dp5"
android:text="" />
</android.support.v7.widget.CardView>
<LinearLayout
android:id="#+id/quizQuestionLL"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/questionQuizCV"
android:layout_marginTop="#dimen/dp5"
android:orientation="vertical"
android:padding="#dimen/dp5" />
</RelativeLayout>
</ScrollView>
& this is the item_layout.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mcqlayout_LL"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="#dimen/dp5"
android:minHeight="30dp"
android:orientation="horizontal"
android:paddingBottom="#dimen/dp7"
android:paddingTop="#dimen/dp7">
<TextView
android:id="#+id/optioniconTV"
android:layout_width="#dimen/dp40"
android:layout_height="#dimen/dp40"
android:background="#drawable/circle_bg"
android:gravity="center"
android:padding="#dimen/dp3"
android:text="A"
android:textSize="#dimen/sub_heading_text_size" />
<com.emedicoz.app.CustomViews.JustifiedTextView
android:id="#+id/optionTextTV"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="#dimen/dp10" />
</LinearLayout>
This is the CustomTextView that I have created to show the HTML content directly. The JustifiedTextView.class is
public class JustifiedTextView extends WebView {
private String text = "";
private int textSize = 12;
private int backgroundColor = Color.TRANSPARENT;
public JustifiedTextView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setWebChromeClient(new WebChromeClient() {});
}
public void setText(String s) {
this.text = s;
reloadData();
}
#SuppressLint("NewApi")
private void reloadData() {
// loadData(...) has a bug showing utf-8 correctly. That's why we need to set it first.
this.getSettings().setDefaultTextEncodingName("utf-8");
// this.loadData(String.format(core,textColor,textSize,text), "text/html","utf-8");
this.loadData(text, "text/html", "utf-8");
// set WebView's background color *after* data was loaded.
super.setBackgroundColor(backgroundColor);
// Hardware rendering breaks background color to work as expected.
// Need to use software renderer in that case.
if (android.os.Build.VERSION.SDK_INT >= 11)
this.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
public void setTextSize(int textSize) {
this.textSize = textSize;
reloadData();
}
}
I have tried All of the solutions mentioned below.
Disable WebView touch events in Android
Already tried to set the android:descendantFocusability="blocksDescendants" to ScrollView
Why the click Event of LinearLayout does not fire when making click on WebView?
This is the way I am inflating the Layout and handle the click event.
private LinearLayout initAnswerMCViews(String text, String questions, Questions questionsModel) {
LinearLayout view = (LinearLayout) View.inflate(activity, R.layout.mcq_quiz, null);
answerTV = (JustifiedTextView) view.findViewById(R.id.optionTextTV);
optionIconTV = (TextView) view.findViewById(R.id.optioniconTV);
mcqItemLL = (LinearLayout) view.findViewById(R.id.mcqlayout_LL);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
lp.setMargins(3, 3, 3, 3);
mcqItemLL.setLayoutParams(lp);
if (questionsModel.isAnswered()) {
String[] answer = questionsModel.getUser_answer().split(",");
for (int i = 0; i < answer.length; i++) {
if (answer[i].equals(text)) {
answerTV.setText(questions);
optionIconTV.setText(text);
optionIconTV.setBackgroundResource(R.drawable.circle_bg_true);
} else {
answerTV.setText(questions);
optionIconTV.setText(text);
}
}
} else {
answerTV.setText(questions);
optionIconTV.setText(text);
}
mcqItemLL.setTag(R.id.questions, optionIconTV.getText().toString());
mcqItemLL.setTag(R.id.optionsAns, mcqItemLL);
mcqItemLL.setOnClickListener(optionClickListener);
viewArrayList.add(mcqItemLL);
return view;
}
Why the click is not get listen when clicked on the WebView part in the Layout?
I finally found the issue.
It is basically the issue of conflicting Touch Event of both parent Scrollview and CustomWebView.
So, by using the new classes that itself overriding the ClickListener and TouchListener.
package com.app.CustomViews;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
public class TestSeriesOptionWebView extends WebView implements GestureDetector.OnGestureListener {
private boolean check = false;
class LongClick implements OnLongClickListener {
final /* synthetic */ TestSeriesOptionWebView testSeriesOptionWebView;
LongClick(TestSeriesOptionWebView testSeriesOptionWebView) {
this.testSeriesOptionWebView = testSeriesOptionWebView;
}
public boolean onLongClick(View view) {
return true;
}
}
public TestSeriesOptionWebView(Context context) {
super(context);
handleClick();
}
public TestSeriesOptionWebView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
handleClick();
}
public TestSeriesOptionWebView(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
handleClick();
}
private void handleClick() {
setFocusable(false);
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}*/
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
setVerticalScrollBarEnabled(false);
setBackgroundColor(0);
setHapticFeedbackEnabled(false);
setOnLongClickListener(new LongClick(this));
}
public void setDisableWebViewTouchListener(boolean z) {
this.check = z;
}
public boolean onTouchEvent(MotionEvent motionEvent) {
if (this.check) {
return false;
}
return super.onTouchEvent(motionEvent);
}
public boolean canScrollHorizontal(int i) {
int computeHorizontalScrollOffset = computeHorizontalScrollOffset();
int computeHorizontalScrollRange = computeHorizontalScrollRange() - computeHorizontalScrollExtent();
if (computeHorizontalScrollRange == 0) {
return false;
}
if (i < 0) {
if (computeHorizontalScrollOffset <= 0) {
return false;
}
return true;
} else if (computeHorizontalScrollOffset >= computeHorizontalScrollRange - 1) {
return false;
} else {
return true;
}
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
public boolean onDown(MotionEvent motionEvent) {
return true;
}
public void onShowPress(MotionEvent motionEvent) {
}
public boolean onSingleTapUp(MotionEvent motionEvent) {
return false;
}
public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent2, float f, float f2) {
return f != 0.0f;
}
public void onLongPress(MotionEvent motionEvent) {
}
public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent2, float f, float f2) {
return true;
}
}
If anybody faces this type of issue, then Use this Custom Class TestSeriesOptionWebView.class.

How to create a whatsapp like recording button with slide to cancel

As in whatsapp I need a recoding button and a slide to cancel and fade animation , I have searched for similar code but didn't got one.
I am new to android programming any help or link could be helpful.
I have created a github project.You can take a look at it https://github.com/sarathnk/Audio
audioSendButton.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) slideText
.getLayoutParams();
params.leftMargin = dp(30);
slideText.setLayoutParams(params);
ViewProxy.setAlpha(slideText, 1);
startedDraggingX = -1;
// startRecording();
startrecord();
audioSendButton.getParent()
.requestDisallowInterceptTouchEvent(true);
recordPanel.setVisibility(View.VISIBLE);
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP
|| motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
startedDraggingX = -1;
stoprecord();
// stopRecording(true);
} else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE) {
float x = motionEvent.getX();
if (x < -distCanMove) {
stoprecord();
// stopRecording(false);
}
x = x + ViewProxy.getX(audioSendButton);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) slideText
.getLayoutParams();
if (startedDraggingX != -1) {
float dist = (x - startedDraggingX);
params.leftMargin = dp(30) + (int) dist;
slideText.setLayoutParams(params);
float alpha = 1.0f + dist / distCanMove;
if (alpha > 1) {
alpha = 1;
} else if (alpha < 0) {
alpha = 0;
}
ViewProxy.setAlpha(slideText, alpha);
}
if (x <= ViewProxy.getX(slideText) + slideText.getWidth()
+ dp(30)) {
if (startedDraggingX == -1) {
startedDraggingX = x;
distCanMove = (recordPanel.getMeasuredWidth()
- slideText.getMeasuredWidth() - dp(48)) / 2.0f;
if (distCanMove <= 0) {
distCanMove = dp(80);
} else if (distCanMove > dp(80)) {
distCanMove = dp(80);
}
}
}
if (params.leftMargin > dp(30)) {
params.leftMargin = dp(30);
slideText.setLayoutParams(params);
ViewProxy.setAlpha(slideText, 1);
startedDraggingX = -1;
}
}
view.onTouchEvent(motionEvent);
return true;
}
});
you can use the library that i have made RecordView
it's easy to setup and it's simulates the same behavior like WhatsApp.
Simply add the Views RecordView and RecordButton
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/parent_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.devlomi.recordview.MainActivity">
<com.devlomi.record_view.RecordView
android:id="#+id/record_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toLeftOf="#id/record_button"
app:slide_to_cancel_arrow="#drawable/ic_keyboard_arrow_left"
app:slide_to_cancel_text="Slide To Cancel"
app:slide_to_cancel_margin_right="10dp"/>
<com.devlomi.record_view.RecordButton
android:id="#+id/record_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:background="#drawable/bg_mic"
android:scaleType="centerInside"
app:src="#drawable/ic_mic_white"
/>
then in your Activity
RecordView recordView = (RecordView) findViewById(R.id.record_view);
RecordButton recordButton = (RecordButton)
findViewById(R.id.record_button);
//IMPORTANT
recordButton.setRecordView(recordView);
lastly you can handle the Record States
onStart when start Recording
onCancel when swiping to cancel
onFinish when finishes record and it returns the recorded time in millis
onLessThanSecond when the record time <= 1Second
recordView.setOnRecordListener(this);
#Override
public void onStart() {
//Start Recording..
Log.d("RecordView", "onStart");
}
#Override
public void onCancel() {
//On Swipe To Cancel
Log.d("RecordView", "onCancel");
}
#Override
public void onFinish(long recordTime) {
//Stop Recording..
String time = getHumanTimeText(recordTime);
Log.d("RecordView", "onFinish");
Log.d("RecordTime", time);
}
#Override
public void onLessThanSecond() {
//When the record time is less than One Second
Log.d("RecordView", "onLessThanSecond");
}
You can put a scale animation on the button and touch gestures to detect the user's movements..
Checkout the sample here..
https://github.com/varunjohn/Audio-Recording-Animation
This sample also has delete animation and lock feature similar to whatsapp..
Check Sample code here
imageViewAudio.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (isDeleting) {
return true;
}
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
cancelOffset = (float) (imageViewAudio.getX() / 2.8);
lockOffset = (float) (imageViewAudio.getX() / 2.5);
if (firstX == 0) {
firstX = motionEvent.getRawX();
}
if (firstY == 0) {
firstY = motionEvent.getRawY();
}
startRecord();
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP
|| motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
stopRecording(RecordingBehaviour.RELEASED);
}
} else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE) {
if (stopTrackingAction) {
return true;
}
UserBehaviour direction = UserBehaviour.NONE;
float motionX = Math.abs(firstX - motionEvent.getRawX());
float motionY = Math.abs(firstY - motionEvent.getRawY());
if (motionX > directionOffset &&
motionX > directionOffset &&
lastX < firstX && lastY < firstY) {
if (motionX > motionY && lastX < firstX) {
direction = UserBehaviour.CANCELING;
} else if (motionY > motionX && lastY < firstY) {
direction = UserBehaviour.LOCKING;
}
} else if (motionX > motionY && motionX > directionOffset && lastX < firstX) {
direction = UserBehaviour.CANCELING;
} else if (motionY > motionX && motionY > directionOffset && lastY < firstY) {
direction = UserBehaviour.LOCKING;
}
if (direction == UserBehaviour.CANCELING) {
if (userBehaviour == UserBehaviour.NONE || motionEvent.getRawY() + imageViewAudio.getWidth() / 2 > firstY) {
userBehaviour = UserBehaviour.CANCELING;
}
if (userBehaviour == UserBehaviour.CANCELING) {
translateX(-(firstX - motionEvent.getRawX()));
}
} else if (direction == UserBehaviour.LOCKING) {
if (userBehaviour == UserBehaviour.NONE || motionEvent.getRawX() + imageViewAudio.getWidth() / 2 > firstX) {
userBehaviour = UserBehaviour.LOCKING;
}
if (userBehaviour == UserBehaviour.LOCKING) {
translateY(-(firstY - motionEvent.getRawY()));
}
}
lastX = motionEvent.getRawX();
lastY = motionEvent.getRawY();
}
view.onTouchEvent(motionEvent);
return true;
}
});
I have implemented the send button as in whatsapp application which can be either in send state or record state. You can take a look at it here on my blog post.
The usage is very simple.
<com.gunhansancar.android.animbutton.AnimButton
android:id="#+id/animButton"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_width="50dp"
android:layout_height="50dp"
app:first="#drawable/ic_mic"
app:second="#drawable/ic_send" />
You just have to set first and second drawable. And also you have to set the state by calling goToState() method.
I used the code provided by #3llomi to create a class that only produces the expanding button without the cancel on slide animation. As I wanted to place my buttons inside a recyclerview the cancel animation would just have overcrowded things. My code comes with a callback that delivers an audiofile stored in cache when recording is completed
these are the necessary clases and xmls:
public class RecordButton extends AppCompatImageView implements View.OnTouchListener{
private ScaleAnim scaleAnim;
private boolean listenForRecord = true;
private int commId;
private MediaRecorder recorder = null;
private boolean isRecording;
private static SoundPool soundPool = null;
private static int soundStart,soundEnd;
public RecordButton(Context context, int id) {
super(context);
init(context, null);
commId=id;
}
public RecordButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RecordButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RecordButton);
int imageResource = typedArray.getResourceId(R.styleable.RecordButton_mic_icon, -1);
if (imageResource != -1) {
setTheImageResource(imageResource);
}
typedArray.recycle();
}
if (soundPool==null){
soundPool = new SoundPool.Builder()
.setMaxStreams(5)
.build();
//these are just two wav files with short intro and exit sounds
soundStart=soundPool.load(getContext(),R.raw.start_recording_sound,0);
soundEnd=soundPool.load(getContext(),R.raw.end_recording_sound,0);
}
scaleAnim = new ScaleAnim(this);
this.setOnTouchListener(this);
}
private void setTheImageResource(int imageResource) {
Drawable image = AppCompatResources.getDrawable(getContext(), imageResource);
setImageDrawable(image);
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setClip(this);
}
public void setClip(View v) {
if (v.getParent() == null) {
return;
}
if (v instanceof ViewGroup) {
((ViewGroup) v).setClipChildren(false);
((ViewGroup) v).setClipToPadding(false);
}
if (v.getParent() instanceof View) {
setClip((View) v.getParent());
}
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (isListenForRecord()) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
((RecordButton) v).startScale();
soundPool.play(soundStart,1,1,0,0,1);
startRecording();
break;
case MotionEvent.ACTION_UP:
stopRecording();
soundPool.play(soundEnd,1,1,0,0,1);
((RecordButton) v).stopScale();
break;
}
}
return isListenForRecord();
}
File file;
//audio recording tools
private void startRecording() {
if (isRecording){
stopRecording();
}
String fileName=getContext().getExternalCacheDir().getAbsolutePath()+"/"+commId+"_"+new Date().getTime()+".amr";
file=new File(fileName);
recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_WB);
recorder.setOutputFile(fileName);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_WB);
recorder.setAudioSamplingRate(8000);
recorder.setAudioChannels(1);
recorder.setAudioEncodingBitRate(12000);
try {
recorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
Toast.makeText(getContext(),fileName,Toast.LENGTH_LONG).show();
recorder.start();
setIsRecording(true);
}
private void stopRecording() {
if (isRecording) {
recorder.stop();
recorder.release();
setIsRecording(false);
recordingFinishedListener.onRecordingFinished(file);
recorder = null;
}
}
private void setIsRecording(boolean isRecording){
this.isRecording=isRecording;
}
protected void startScale() {
scaleAnim.start();
}
protected void stopScale() {
scaleAnim.stop();
}
public void setListenForRecord(boolean listenForRecord) {
this.listenForRecord = listenForRecord;
}
public boolean isListenForRecord() {
return listenForRecord;
}
//callback for when a recording has been made
public interface RecordingFinishedListener{
void onRecordingFinished(File file);
}
RecordingFinishedListener recordingFinishedListener;
public void setRecordingFinishedListener(RecordingFinishedListener recordingFinishedListener) {
this.recordingFinishedListener = recordingFinishedListener;
}
}
then the class responsible for animations which allows you to define the degree of the expansion
public class ScaleAnim {
private View view;
public ScaleAnim(View view) {
this.view = view;
}
void start() {
AnimatorSet set = new AnimatorSet();
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.3f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1.3f);
set.setDuration(150);
set.setInterpolator(new AccelerateDecelerateInterpolator());
set.playTogether(scaleY, scaleX);
set.start();
}
void stop() {
AnimatorSet set = new AnimatorSet();
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.0f);
// scaleY.setDuration(250);
// scaleY.setInterpolator(new DecelerateInterpolator());
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1.0f);
// scaleX.setDuration(250);
// scaleX.setInterpolator(new DecelerateInterpolator());
set.setDuration(150);
set.setInterpolator(new AccelerateDecelerateInterpolator());
set.playTogether(scaleY, scaleX);
set.start();
}
}
and the attrs.xml file. The rest are just a mic icon and some some sounds that you can find by yourself
<resources>
<declare-styleable name="RecordButton">
<attr name="mic_icon" format="reference" />
</declare-styleable>
</resources>
by setting the RecordingFinishedListener on the recordbutton you can go on to process the recorded sound.

Slider button to accept call in Android

I want to develop my own Accept and Decline buttons for an incoming call. To prevent the call to be accidentally answered or rejected when taking the phone out of the pocket I would like to make a slider style button or something similar. I am, to accept the call is not just to tap on the Accept button. It would be more like sliding the finger from left to right (or opposite) and let the button get wider with the moment. Just like Android does.
Is there any way to make this? Any hint?
I hope to be clear.
How about create an image and slide it to the right (or left) and then send the event to an Activity or any view that you wanna handle the result?
For this, you can created a custom view which implements OnTouchListener :
public class ImageTouchSlider extends RelativeLayout implements View.OnTouchListener {
private Context mContext;
private ImageView mImage;
private int mScreenWidthInPixel;
private int mScreenWidthInDp;
private float mDensity;
private int mPaddingInDp = 15;
private int mPaddingInPixel;
private int mLengthOfSlider;
public interface OnImageSliderChangedListener{
void onChanged();
}
private OnImageSliderChangedListener mOnImageSliderChangedListener;
public ImageTouchSlider(Context context) {
super(context);
mContext = context;
createView();
}
public ImageTouchSlider(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
createView();
}
public ImageTouchSlider(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
createView();
}
public void createView() {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.image_touch_slider, this, true);
mImage = (ImageView) findViewById(R.id.slider_image);
mImage.setOnTouchListener(this);
WindowManager manager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics ();
display.getMetrics(outMetrics);
mDensity = getResources().getDisplayMetrics().density;
float dpWidth = outMetrics.widthPixels / mDensity;
mScreenWidthInPixel = outMetrics.widthPixels;
mScreenWidthInDp = (int) (mScreenWidthInPixel / mDensity);
mLengthOfSlider = (int) (mScreenWidthInDp - mPaddingInDp*2);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
int width = v.getWidth();
float xPos = event.getRawX();
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
// You can add some clicked reaction here.
break;
case MotionEvent.ACTION_MOVE:
if(xPos < (mScreenWidthInPixel - width - mPaddingInDp*mDensity) && xPos > mPaddingInDp*mDensity) {
mOnImageSliderChangedListener.onChanged();
layoutParams.leftMargin = (int) xPos - width / 2;
mImage.setLayoutParams(layoutParams);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return true;
}
public void setOnImageSliderChangedListener(OnImageSliderChangedListener listener) {
mOnImageSliderChangedListener = listener;
}
} //end of class
image_touch_slider.xml layout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="#+id/slider"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:src="#drawable/your_drawable" />
</RelativeLayout>
You can modify screen width calculation part (my current code is not so clean), and add this view in .xml like this :
<com.your.package.path.ImageTouchSlider
android:id="#+id/slider"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
In your class, you can find this view :
ImageTouchSlider slider = (ImageTouchSlider) findViewById(R.id.slider);
slider.setOnImageSliderChangedListener(new ImageTouchSlider.OnImageSliderChangedListener() {
#Override
public void onChanged() {
// do something what you want here.
}
});
Hope this can help! :)
If you have your own sliding layout then see this code, might be helpful for you.
public class UnlockBar extends RelativeLayout
{
private OnUnlockListener listener = null;
private TextView text_label = null;
private ImageView img_thumb = null;
private int thumbWidth = 0;
boolean sliding = false;
private int sliderPosition = 0;
int initialSliderPosition = 0;
float initialSlidingX = 0;
public UnlockBar(Context context)
{
super(context);
init(context, null);
}
public UnlockBar(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context, attrs);
}
public UnlockBar(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init(context, attrs);
}
public void setOnUnlockListener(OnUnlockListener listener)
{
this.listener = listener;
}
public void reset()
{
final RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) img_thumb.getLayoutParams();
ValueAnimator animator = ValueAnimator.ofInt(params.leftMargin, 0);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator)
{
params.leftMargin = (Integer) valueAnimator.getAnimatedValue();
img_thumb.requestLayout();
}
});
animator.setDuration(300);
animator.start();
text_label.setAlpha(1f);
}
private void init(Context context, AttributeSet attrs)
{
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.unlock_main, this, true);
// Retrieve layout elements
text_label = (TextView) findViewById(R.id.text_label);
img_thumb = (ImageView) findViewById(R.id.img_thumb);
// Get padding
thumbWidth = dpToPx(80); // 60dp + 2*10dp
}
#Override
#SuppressLint("ClickableViewAccessibility")
public boolean onTouchEvent(MotionEvent event)
{
super.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if (event.getX() > sliderPosition && event.getX() < (sliderPosition + thumbWidth))
{
sliding = true;
initialSlidingX = event.getX();
initialSliderPosition = sliderPosition;
}
}
else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_OUTSIDE)
{
if (sliderPosition >= (getMeasuredWidth() - thumbWidth))
{
if (listener != null) listener.onUnlock();
}
else
{
sliding = false;
sliderPosition = 0;
reset();
}
}
else if (event.getAction() == MotionEvent.ACTION_MOVE && sliding)
{
sliderPosition = (int) (initialSliderPosition + (event.getX() - initialSlidingX));
if (sliderPosition <= 0) sliderPosition = 0;
if (sliderPosition >= (getMeasuredWidth() - thumbWidth))
{
sliderPosition = (int) (getMeasuredWidth() - thumbWidth);
}
else
{
int max = (int) (getMeasuredWidth() - thumbWidth);
int progress = (int) (sliderPosition * 100 / (max * 1.0f));
text_label.setAlpha(1f - progress * 0.02f);
}
setMarginLeft(sliderPosition);
}
return true;
}
private void setMarginLeft(int margin)
{
if (img_thumb == null) return;
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) img_thumb.getLayoutParams();
params.setMargins(margin, 0, 0, 0);
img_thumb.setLayoutParams(params);
}
private int dpToPx(int dp)
{
float density = getResources().getDisplayMetrics().density;
return Math.round((float)dp * density);
}
public static interface OnUnlockListener {
void onUnlock();
}
}
And just set the listener in main activity
UnlockBar unlock = (UnlockBar) findViewById(R.id.unlock);
// Attach listener
unlock.setOnUnlockListener(new OnUnlockListener() {
#Override
public void onUnlock()
{
Toast.makeText(TestActivity.this, "You've successfully unlocked it !", Toast.LENGTH_LONG).show();
}
});
And draw your own slide_image_layout.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="80dp"
android:layout_width="match_parent"
android:background="#000000"
android:padding="10dp">
<ImageView
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_alignParentLeft="true"
android:src="#drawable/unlock_left"
android:contentDescription="#string/unlock_locked" />
<ImageView
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:src="#drawable/unlock_right"
android:contentDescription="#string/unlock_unlocked" />
<TextView
android:id="#+id/text_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="#string/unlock_instructions"
android:textColor="#android:color/white"
android:textSize="18sp"
android:textStyle="italic" />
<ImageView
android:id="#+id/img_thumb"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:src="#drawable/unlock_thumb"
android:contentDescription="#string/unlock_thumb" />
</RelativeLayout>
And in your main_layout.xml add this ..
<com.hamondigital.unlock.UnlockBar
android:id="#+id/unlock"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

Implementing a slider (SeekBar) in Android

I want to implement a slider, which is basically two lines, one vertical and one horizontal, crossing where the screen is touched. I have managed to make one but I have to issues:
The slider is not very smooth, there is a slight delay when I'm moving the finger
If I place two sliders it is not multitouch, and I'd like to use both of them simultaneously
Here is the code:
public class Slider extends View {
private Controller controller = new Controller();
private boolean initialisedSlider;
private int sliderWidth, sliderHeight;
private Point pointStart;
private Paint white;
private int mode;
final static int VERTICAL = 0, HORIZONTAL = 1, BOTH = 2;
public Slider(Context context) {
super(context);
setFocusable(true);
// TODO Auto-generated constructor stub
}
public Slider(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
pointStart = new Point();
initialisedSlider = false;
mode = Slider.BOTH;
}
#Override
protected void onDraw(Canvas canvas) {
if(!initialisedSlider) {
initialisedSlider = true;
sliderWidth = getMeasuredWidth();
sliderHeight = getMeasuredHeight();
pointStart.x = (int)(sliderWidth/2.0);
pointStart.y = (int)(sliderHeight/2.0);
controller = new Controller(pointStart, 3);
white = new Paint();
white.setColor(0xFFFFFFFF);
}
canvas.drawLine(controller.getCoordX(),0,
controller.getCoordX(),sliderHeight,
white);
canvas.drawLine(0, controller.getCoordY(),
sliderWidth, controller.getCoordY(),
white);
}
public boolean onTouchEvent(MotionEvent event) {
int eventaction = event.getAction();
int X = (int)event.getX();
int Y = (int)event.getY();
switch (eventaction) {
case MotionEvent.ACTION_DOWN:
if(isInBounds(X,Y)) {
updateController(X, Y);
}
break;
case MotionEvent.ACTION_MOVE:
if(isInBounds(X,Y)) {
updateController(X, Y);
}
break;
case MotionEvent.ACTION_UP:
if(isInBounds(X,Y)) {
updateController(X, Y);
}
break;
}
invalidate();
return true;
}
private boolean isInBounds(int x, int y) {
return ((x<=(sliderWidth)) && (x>=(0))
&& (y<=(sliderHeight)) && (y>=(0)));
}
private void updateController(int x, int y) {
switch(mode) {
case Slider.HORIZONTAL:
controller.setCoordX(x);
break;
case Slider.VERTICAL:
controller.setCoordY(y);
break;
case Slider.BOTH:
controller.setCoordX(x);
controller.setCoordY(y);
break;
}
}
private class Controller {
private int coordX, coordY;
Controller() {
}
Controller(Point point, int width) {
setCoordX(point.x);
setCoordY(point.y);
}
public void setCoordX(int coordX) {
this.coordX = coordX;
}
public int getCoordX() {
return coordX;
}
public void setCoordY(int coordY) {
this.coordY = coordY;
}
public int getCoordY() {
return coordY;
}
}
}
And the XML file:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/hello" />
<com.android.lasttest.Slider
android:id="#+id/slider"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"/>
<com.android.lasttest.Slider
android:id="#+id/slider"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"/>
<com.android.lasttest.Slider
android:id="#+id/slider"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"/>
</LinearLayout>
How to implement a SeekBar
Add the SeekBar to your layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/textView"
android:layout_margin="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<SeekBar
android:id="#+id/seekBar"
android:max="100"
android:progress="50"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Notes
max is the highest value that the seek bar can go to. The default is 100. The minimum is 0. The xml min value is only available from API 26, but you can just programmatically convert the 0-100 range to whatever you need for earlier versions.
progress is the initial position of the slider dot (called a "thumb").
For a vertical SeekBar use android:rotation="270".
Listen for changes in code
public class MainActivity extends AppCompatActivity {
TextView tvProgressLabel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// set a change listener on the SeekBar
SeekBar seekBar = findViewById(R.id.seekBar);
seekBar.setOnSeekBarChangeListener(seekBarChangeListener);
int progress = seekBar.getProgress();
tvProgressLabel = findViewById(R.id.textView);
tvProgressLabel.setText("Progress: " + progress);
}
SeekBar.OnSeekBarChangeListener seekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// updated continuously as the user slides the thumb
tvProgressLabel.setText("Progress: " + progress);
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
// called when the user first touches the SeekBar
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
// called after the user finishes moving the SeekBar
}
};
}
Notes
If you don't need to do any updates while the user is moving the seekbar, then you can just update the UI in onStopTrackingTouch.
See also
SeekBar Tutorial With Example In Android Studio
Android provides slider which is horizontal
http://developer.android.com/reference/android/widget/SeekBar.html
and implement OnSeekBarChangeListener
http://developer.android.com/reference/android/widget/SeekBar.OnSeekBarChangeListener.html
If you want vertical Seekbar then follow this link
http://hoodaandroid.blogspot.in/2012/10/vertical-seek-bar-or-slider-in-android.html
For future readers!
Starting from material components android 1.2.0-alpha01, you have slider component
ex:
<com.google.android.material.slider.Slider
android:id="#+id/slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:valueFrom="20f"
android:valueTo="70f"
android:stepSize="10" />
Release notes
Material Design Spec
Docs

Categories

Resources