Setting width of SeekBar to make "swipe to unlock" effect
Asked Answered
L

2

9

I am attempting to make a swipe to unlock feature using a SeekBar. The look I am aiming for is shown here:

enter image description here

This is composed of two images, a background, and a button. I put both the background and the SeekBar in a FrameLayout so that the SeekBar should sit on top of the background.

Like so:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:text="Testing 123..." />

    <FrameLayout
        android:layout_height="wrap_content"
        android:layout_width="wrap_content" >

        <ImageView
            android:id="@+id/ImageView01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="center"
            android:src="@drawable/unlockback" />

        <SeekBar
            android:id="@+id/myseek"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clickable="false"
            android:max="100"
            android:progressDrawable="@android:color/transparent"
            android:thumb="@drawable/unlockbut" />

    </FrameLayout>

</LinearLayout>

Unfortunately the end result looks like this (in eclipse):

enter image description here

I seem to be unable to make the SeekBar match the size of the FrameLayout. You can see the size of the Seekbar represented by a thin blue frame in the image above. The frame has two small solid blue squares which you can grab with the mouse pointer for resizing. But if I use my mouse pointer to drag the little blue square to match the full width of the FrameView, then as soon as I let go of the mouse, the square pings back to its original (too small) size.

What can I do to fix this?.. If I can achieve swipe to unlock in a fundamentally different way, then I'm interested in that too.

Letterhead answered 26/4, 2015 at 9:50 Comment(8)
Did you already found solution for this?Suetonius
@Techfist: No... so I just started a bounty.Letterhead
@Luksprog: That all sounds good (I can not test it right now), but anyhow, you should cut and paste your "comment" into an "answer" otherwise you can not collect the bounty.Letterhead
I am not sure you should be using a progress bar anyway :/Deniable
Can you provide me the thumb image and I will see what I can do?Deniable
@Bojan Kseneman: Ok, file are here => rai-software.com/filesLetterhead
@Luksprog: I have some issues and question about your code, but I don't want to start a series of comments here... please change your "comment" to an "answer".Letterhead
Would you consider using a SlidingDrawer instead of this? Its 10 x easier to doMonophagous
D
9

As I promised I will see what I can do. I have not used your images and used android graphics to do the drawing as that makes the whole thing more customizable and and scalable. If you do insist in drawing your images, use canvas.drawBitmap... it's pretty simple. The main logic can stay the same.

I may come back and add some fancy animations and visual effects, but for now I left some commented out code to play with shaders and gradients as I am a bit short on time at the moment.

Let's get to it... First crate attrs.xml in /resources/ and add this to it.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SlideToUnlock">
        <attr name="sliderColor" format="color"/>
        <attr name="cancelOnYExit" format="boolean"/>
        <attr name="slideToUnlockText" format="string"/>
        <attr name="slideToUnlockTextColor" format="color"/>
        <attr name="slideToUnlockBackgroundColor" format="color"/>
        <attr name="cornerRadiusX" format="dimension"/>
        <attr name="cornerRadiusY" format="dimension"/>
    </declare-styleable>
</resources>

And then SlideToUnlock.java

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.EmbossMaskFilter;
import android.graphics.MaskFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Typeface;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by ksenchy on 29.4.2015.
 */
public class SlideToUnlock extends View {

    public interface OnSlideToUnlockEventListener {
        public void onSlideToUnlockCanceled();

        public void onSlideToUnlockDone();
    }

    private int measuredWidth, measuredHeight;
    private float density;
    private OnSlideToUnlockEventListener externalListener;
    private Paint mBackgroundPaint, mTextPaint, mSliderPaint;
    private float rx, ry; // Corner radius
    private Path mRoundedRectPath;
    private String text = "Unlock  →";

    float x;
    float event_x, event_y;
    float radius;
    float X_MIN, X_MAX;
    private boolean ignoreTouchEvents;

    // Do we cancel when the Y coordinate leaves the view?
    private boolean cancelOnYExit;
    private boolean useDefaultCornerRadiusX, useDefaultCornerRadiusY;


    /**
     * Default values *
     */
    int backgroundColor = 0xFF807B7B;
    int textColor = 0xFFFFFFFF;
    int sliderColor = 0xAA404040;


    public SlideToUnlock(Context context) {
        super(context);
        init(context, null, 0);
    }

    public SlideToUnlock(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
    }

    public SlideToUnlock(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }

    public OnSlideToUnlockEventListener getExternalListener() {
        return externalListener;
    }

    public void setExternalListener(OnSlideToUnlockEventListener externalListener) {
        this.externalListener = externalListener;
    }

    private void init(Context context, AttributeSet attrs, int style) {

        Resources res = getResources();
        density = res.getDisplayMetrics().density;

        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SlideToUnlock, style, 0);

        String tmp = a.getString(R.styleable.SlideToUnlock_slideToUnlockText);
        text = TextUtils.isEmpty(tmp) ? text : tmp;
        rx = a.getDimension(R.styleable.SlideToUnlock_cornerRadiusX, rx);
        useDefaultCornerRadiusX = rx == 0;
        ry = a.getDimension(R.styleable.SlideToUnlock_cornerRadiusX, ry);
        useDefaultCornerRadiusY = ry == 0;
        backgroundColor = a.getColor(R.styleable.SlideToUnlock_slideToUnlockBackgroundColor, backgroundColor);
        textColor = a.getColor(R.styleable.SlideToUnlock_slideToUnlockTextColor, textColor);
        sliderColor = a.getColor(R.styleable.SlideToUnlock_sliderColor, sliderColor);
        cancelOnYExit = a.getBoolean(R.styleable.SlideToUnlock_cancelOnYExit, false);

        a.recycle();

        mRoundedRectPath = new Path();

        mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBackgroundPaint.setStyle(Paint.Style.FILL);
        mBackgroundPaint.setColor(backgroundColor);

        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setColor(textColor);
        mTextPaint.setTypeface(Typeface.create("Roboto-Thin", Typeface.NORMAL));

        mSliderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mSliderPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mSliderPaint.setColor(sliderColor);
        mSliderPaint.setStrokeWidth(2 * density);

        if (!isInEditMode()) {
            // Edit mode does not support shadow layers
            // mSliderPaint.setShadowLayer(10.0f, 0.0f, 2.0f, 0xFF000000);
            //mSliderPaint.setMaskFilter(new EmbossMaskFilter(new float[]{1, 1, 1}, 0.4f, 10, 8.2f));
            float[] direction = new float[]{0.0f, -1.0f, 0.5f};
            MaskFilter filter = new EmbossMaskFilter(direction, 0.8f, 15f, 1f);
            mSliderPaint.setMaskFilter(filter);
            //mSliderPaint.setShader(new LinearGradient(8f, 80f, 30f, 20f, Color.RED,Color.WHITE, Shader.TileMode.MIRROR));
            //mSliderPaint.setShader(new RadialGradient(8f, 80f, 90f, Color.RED,Color.WHITE, Shader.TileMode.MIRROR));
            //mSliderPaint.setShader(new SweepGradient(80, 80, Color.RED, Color.WHITE));
            //mSliderPaint.setMaskFilter(new BlurMaskFilter(15, BlurMaskFilter.Blur.OUTER));
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measuredHeight = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        measuredWidth = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);

        if (useDefaultCornerRadiusX) {
            rx = measuredHeight * 0.52f;
        }
        if (useDefaultCornerRadiusY) {
            ry = measuredHeight * 0.52f;
        }
        mTextPaint.setTextSize(measuredHeight / 3.0f);

        radius = measuredHeight * 0.45f;
        X_MIN = 1.2f * radius;
        X_MAX = measuredWidth - X_MIN;
        x = X_MIN;

        setMeasuredDimension(measuredWidth, measuredHeight);
    }

    private void drawRoundRect(Canvas c) {
        mRoundedRectPath.reset();
        mRoundedRectPath.moveTo(rx, 0);
        mRoundedRectPath.lineTo(measuredWidth - rx, 0);
        mRoundedRectPath.quadTo(measuredWidth, 0, measuredWidth, ry);
        mRoundedRectPath.lineTo(measuredWidth, measuredHeight - ry);
        mRoundedRectPath.quadTo(measuredWidth, measuredHeight, measuredWidth - rx, measuredHeight);
        mRoundedRectPath.lineTo(rx, measuredHeight);
        mRoundedRectPath.quadTo(0, measuredHeight, 0, measuredHeight - ry);
        mRoundedRectPath.lineTo(0, ry);
        mRoundedRectPath.quadTo(0, 0, rx, 0);
        c.drawPath(mRoundedRectPath, mBackgroundPaint);
    }

    @SuppressLint("NewApi")
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (measuredHeight <= 0 || measuredWidth <= 0) {
            // There is not much we can draw :/
            return;
        }

        if (Build.VERSION.SDK_INT >= 21) {
            canvas.drawRoundRect(0, 0, measuredWidth, measuredHeight, rx, ry, mBackgroundPaint);
        }
        else {
            drawRoundRect(canvas);
        }


        // Draw the text in center
        float xPos = ((measuredWidth - mTextPaint.measureText(text)) / 2.0f);
        float yPos = (measuredHeight / 2.0f);
        float titleHeight = Math.abs(mTextPaint.descent() + mTextPaint.ascent());
        yPos += titleHeight / 2.0f;
        canvas.drawText(text, xPos, yPos, mTextPaint);


        canvas.drawCircle(x, measuredHeight * 0.5f, radius, mSliderPaint);

    }

    private void onCancel() {
        reset();
        if (externalListener != null) {
            externalListener.onSlideToUnlockCanceled();
        }
    }

    private void onUnlock() {
        if (externalListener != null) {
            externalListener.onSlideToUnlockDone();
        }
    }

    private void reset() {
        x = X_MIN;
        invalidate();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                ignoreTouchEvents = false;
                reset();
                return true;
            case MotionEvent.ACTION_DOWN:
                // Is within the circle??
                event_x = event.getX(0);
                event_y = event.getY(0);
                double squareRadius = radius * radius;
                double squaredXDistance = (event_x - X_MIN) * (event_x - X_MIN);
                double squaredYDistance = (event_y - measuredHeight / 2) * (event_y - measuredHeight / 2);

                if (squaredXDistance + squaredYDistance > squareRadius) {
                    // User touched outside the button, ignore his touch
                    ignoreTouchEvents = true;
                }

                return true;
            case MotionEvent.ACTION_CANCEL:
                ignoreTouchEvents = true;
                onCancel();
            case MotionEvent.ACTION_MOVE:
                if (!ignoreTouchEvents) {
                    event_x = event.getX(0);
                    if (cancelOnYExit) {
                        event_y = event.getY(0);
                        if (event_y < 0 || event_y > measuredHeight) {
                            ignoreTouchEvents = true;
                            onCancel();
                        }
                    }

                    x = event_x > X_MAX ? X_MAX : event_x < X_MIN ? X_MIN : event_x;
                    if (event_x >= X_MAX) {
                        ignoreTouchEvents = true;
                        onUnlock();
                    }
                    invalidate();
                }
                return true;
            default:
                return super.onTouchEvent(event);
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FF000000">

    <your.package.SlideToUnlock
        android:id="@+id/slideToUnlock"
        android:layout_width="200dp"
        android:layout_height="50dp"
        android:layout_centerInParent="true"/>

    <your.package.SlideToUnlock
        android:id="@+id/slideToUnlock2"
        android:layout_width="200dp"
        android:layout_height="50dp"
        android:layout_below="@+id/slideToUnlock"
        android:layout_centerInParent="true"
        android:layout_marginTop="50dp"
        app:cancelOnYExit="true"
        app:slideToUnlockBackgroundColor="#808080"
        app:slideToUnlockText="Slide to unlock"
        app:slideToUnlockTextColor="#03A9F4"
        app:sliderColor="#AAFFE97F"/>

</RelativeLayout>

MainActivity.java

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.Toast;


public class MainActivity extends ActionBarActivity implements SlideToUnlock.OnSlideToUnlockEventListener {

    private SlideToUnlock slideToUnlockView, slideToUnlockView2;
    private Toast toast;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        slideToUnlockView = (SlideToUnlock) findViewById(R.id.slideToUnlock);
        slideToUnlockView.setExternalListener(this);

        slideToUnlockView2 = (SlideToUnlock) findViewById(R.id.slideToUnlock2);
        slideToUnlockView2.setExternalListener(this);
    }

    private void showToast(String text) {
        if (toast != null) {
            toast.cancel();
        }

        toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
        toast.show();
    }

    @Override
    public void onSlideToUnlockCanceled() {
        showToast("Canceled");
    }

    @Override
    public void onSlideToUnlockDone() {
        showToast("Unlocked");
    }
}

You can download the whole project here. Enjoy :)

This is the final result.

Slide to unlock

Deniable answered 29/4, 2015 at 18:18 Comment(10)
I'm afraid I have no idea what this gradle stuff is all about. I tried putting attrs.xml directly in the resources directory, but eclipse complains "invalid resource directory name" which I guess means it needs to be in some particular directory inside resources... My guess is... layout?Letterhead
Sorry I assumed you are using Android studio. It is using gradle stuff you mentioned. For eclipse just follow step by step tutorial. I don't have eclipse on this pc, but I may try to make an eclipse project when I get home.Deniable
So I put attrs.xml in resources\layout, then created class SlideToUnlock.java... eclipse complains "styleable cannot be resolved or is not a field". Strangely, one other error is "The method drawRoundRect(RectF, float, float, Paint) in the type Canvas is not applicable for the arguments (int, int, int, int, float, float, Paint)"Letterhead
I am using the method added in API level 21, do you have that API? I will fix this later, I was short on time yesterday. Just comment out that if and use the one with path. You should put the attrs.xml in resouces\values not layout and then do a clean and rubuildDeniable
API 21?... hmmm... not very good for a commercial product, that's 60% of the market gone.Letterhead
It is handled with an if clause - if (Build.VERSION.SDK_INT >= 21) {}.. comment this out, it will work on API level 1+Deniable
attrs.xml needs to go in resources\values. This fixes the styleable cannot be resolved issue ... more to follow...Letterhead
No problem. Is this what you were looking with your bounty? :)Deniable
Yes... well, plan A was to solve the mystery of why I could not control the width of the seek bar, but this is a good plan B which suits me fine. After all, I did say "If I can achieve swipe to unlock in a fundamentally different way, then I'm interested in that too."Letterhead
Maybe I'll help you solve you mystery when I have a little more time :)Deniable
B
1

Here are the libraries that may helps you.

https://github.com/Ghedeon/SlideToUnlockProject

Backward answered 4/5, 2015 at 11:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.