Develop/Java

[Java] 자바 android Floating Widget 구현(3)

JunJangE 2021. 7. 12. 15:24

지난번에 위젯을 움직인 후, 그 자리가 아닌 양 사이드로 보내보았다.

 

[Java] 자바 android Floating Widget 구현- 2

지난번에 자바로 android Floating Widget을 구현해보았다. AndroidManifest.xml에서 앱을 다른 모든 앱 위에 표시할 수 있게 권한을 설정하고 Service를 시작하도록 호출하고 enabled를 true로 하여 활.." data-o..

fre2-dom.tistory.com

이번에는 구글링과 공부를 통해 클릭했을 때 앱의 정보가 나오거나 앱 홈 화면으로 다시 돌아가는 방법과 그 외 코드적인 부분을 수정해 구현해보았다.

저번에 했던 위젯 코드랑 비슷하지만 다른 부분이 여러 곳이므로 주석을 잘 보면서 확인하면 좋을 것 같다.

위젯 코드는 다음과 같다.

import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;

public class FloatingWidgetShowService extends Service{

    WindowManager windowManager;
    WindowManager.LayoutParams params ;
    private int x_init_cord, y_init_cord ;
    private final Point szWindow = new Point();
    float height, width;
    long time_start = 0, time_end = 0;
    View floatingView, collapsedView, expandedView;
    ImageView imageClose;


    //플로팅 위젯 보기가 왼쪽에 있는지 오른쪽에 있는지 확인하는 변수
    // 처음에는 플로팅 위젯 보기를 왼쪽에 표시하므로 true로 설정합니다.
    private boolean isLeft = true;


    public FloatingWidgetShowService() {

    }
    @Override
    public IBinder onBind(Intent intent) {

        return null;
    }
    @Override
    public void onCreate() {
        super.onCreate();


        //윈도우매니저 초기화
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);

        // 우리가 만든 플로팅 뷰 레이아웃 확장
        floatingView = LayoutInflater.from(this).inflate(R.layout.floating_widget_layout, null);

        //창에 위젯 아이콘 뷰를 추가
        params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT);

        // 보기 위치 지정
        // 처음에는보기가 왼쪽 중앙에 추가되며 필요에 따라 x-y 좌표를 변경
        params.gravity = Gravity.CENTER | Gravity.LEFT;
        params.x = 0;
        params.y = 100;


        //창에 제거 이미지 뷰를 추가합니다.
        WindowManager.LayoutParams imageParams = new WindowManager.LayoutParams( 140,
                140,
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT);

        // 보기 위치 지정
        imageParams.gravity = Gravity.BOTTOM|Gravity.CENTER;
        imageParams.y = 100;


        // 제거 이미지 정보
        imageClose = new ImageView(this);
        imageClose.setImageResource(R.drawable.close_0);

        // //초기에는 제거 이미지가 표시되지 않으므로 가시성을 GONE으로 설정
        imageClose.setVisibility(View.GONE);

        //윈도우에 뷰 추가
        windowManager.addView(imageClose, imageParams);
        windowManager.addView(floatingView, params);

        // 접힌 뷰 레이아웃의 ID 찾기
        expandedView = floatingView.findViewById(R.id.Layout_Expended);
        collapsedView = floatingView.findViewById(R.id.Layout_Collapsed);

        // 뷰 높이, 너비
        height = windowManager.getDefaultDisplay().getHeight();
        width  = windowManager.getDefaultDisplay().getWidth();


        // 확장된 위젯을 클릭할 경우
        expandedView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                collapsedView.setVisibility(View.VISIBLE);
                expandedView.setVisibility(View.GONE);
            }
        });


        // 사용자가 확장 아이콘을 클릭하면 시작화면으로 이동
        floatingView.findViewById(R.id.Widget_expand_Icon).setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {

                Intent intent = new Intent(FloatingWidgetShowService.this, MainActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
                stopSelf();

            }
        });

        //사용자의 터치 동작을 사용하여 플로팅 뷰를 드래그 앤 이동합니다.
        floatingView.findViewById(R.id.MainParentRelativeLayout).setOnTouchListener(new View.OnTouchListener() {

            int X_Axis, Y_Axis;
            float TouchX, TouchY;
            boolean inBounded = false; // 플로팅 뷰가 뷰를 제거할 경계인지 판단하는 변수

            @Override
            public boolean onTouch(View v, MotionEvent event) {

                // 터치 위치 좌표 가져오기(위젯 클릭 이벤트)
                int x_cord = (int) event.getRawX();
                int y_cord = (int) event.getRawY();

                // 제거 이미지 뷰 절대 좌표
                int[] imageClose_location = new int[2];
                imageClose.getLocationOnScreen(imageClose_location);
                Log.d("Tag", String.valueOf(imageClose_location[0]));
                Log.d("Tag", String.valueOf(imageClose_location[1]));

                // 위젯 뷰 절대 좌표
                int[] widget_location = new int[2];
                floatingView.getLocationOnScreen(widget_location);
                Log.d("ttt", String.valueOf(widget_location[0]));
                Log.d("ttt", String.valueOf(widget_location[1]));


                switch (event.getAction()) {


                    // ACTION_DOWN(View 를 손으로 누르기 시작하는 시점)
                    case MotionEvent.ACTION_DOWN:

                        // 제거 이미지 보임
                        imageClose.setVisibility(View.VISIBLE);


                        // 시작 시간
                        time_start = System.currentTimeMillis();

                        // 현재 좌표(위젯 클릭 이벤트)
                        x_init_cord = x_cord;
                        y_init_cord = y_cord;

                        //터치 위치 좌표 가져오기(위젯 드로우)
                        TouchX = event.getRawX();
                        TouchY = event.getRawY();

                        // 현재 좌표(위젯 드로우)
                        X_Axis = params.x;
                        Y_Axis = params.y;

                    return true;


                    // ACTION_MOVE(View 를 손으로 누르고 드래그 하는 시점)
                    case MotionEvent.ACTION_UP:

                        // 제거 이미지 숨김
                        imageClose.setVisibility(View.GONE);

                        // 초기 좌표와 현재 좌표의 차이 구하기
                        int x_diff = x_cord - x_init_cord;
                        int y_diff = y_cord - y_init_cord;

                        // 위젯 클릭 이벤트
                        // 클릭하는 동안 요소가 약간 움직이기 때문에 x_diff <5 && y_diff< 5를 확인
                        if (Math.abs(x_diff) < 5 && Math.abs(y_diff) < 5) {

                            // 종료 시간
                            time_end = System.currentTimeMillis();

                            // 또한 시작 시간과 종료 시간의 차이가 300ms 미만이어야 하는지 확인
                            if ((time_end - time_start) < 300)
                                collapsedView.setVisibility(View.GONE);
                                expandedView.setVisibility(View.VISIBLE);
                        }

                        // 이미지 뷰 절대 좌표가 조건 안에 있으면 제거한다.
                        if (imageClose_location[0] - imageParams.height  <= widget_location[0] && imageClose_location[0] + imageParams.height  >= widget_location[0]
                            && imageClose_location[1] - imageParams.width  <= widget_location[1] && imageClose_location[1] + imageParams.width  >= widget_location[1]){
                            stopSelf();

                        }else {

                            // 사용자가 플로팅 뷰를 드래그하면 위치 재설정
                            resetPosition(x_cord);
                        }


                        return true;

                    // ACTION_UP(View 로부터 손을 뗀 시점)
                    case MotionEvent.ACTION_MOVE:

                        // 위젯 드로우
                        params.x = X_Axis + (int) (event.getRawX() - TouchX);
                        params.y = Y_Axis + (int) (event.getRawY() - TouchY);



                        // 새로운 X & Y 좌표로 레이아웃 업데이트
                        windowManager.updateViewLayout(floatingView, params);

                        // 제거 이미지에 가까이 가면 이미지 전환
                        if (imageClose_location[0] - imageParams.height  <= widget_location[0] && imageClose_location[0] + imageParams.height  >= widget_location[0]
                                && imageClose_location[1] - imageParams.width  <= widget_location[1] && imageClose_location[1] + imageParams.width  >= widget_location[1]){

                            imageClose.setImageResource(R.drawable.close_1);

                        }
                        else {
                            imageClose.setImageResource(R.drawable.close_0);

                        }

                    return true;
                }
                return false;
            }
        });
    }

    /* 드래깅 시 플로팅 위젯 보기의 위치 재설정 */
    private void resetPosition(int x_cord_now) {
        if (x_cord_now <= width / 2) {
            isLeft = true;
            moveToLeft(x_cord_now);
        } else {
            isLeft = false;
            moveToRight(x_cord_now);
        }

    }

    /* 플로팅 위젯 보기를 왼쪽으로 이동하는 방법 */
    private void moveToLeft(final int current_x_cord) {
        final int x = (int) (width - current_x_cord);

        // 움직이는 횟수와 시간 0으로 초기화
        new CountDownTimer(0, 0) {

            // 플로팅 뷰 매개변수 제거 가져오기
            WindowManager.LayoutParams mParams = (WindowManager.LayoutParams) floatingView.getLayoutParams();

            public void onTick(long t) {
                long step = (500 - t) / 5;

                mParams.x = -(int) (current_x_cord * current_x_cord * step);

                // 새로운 X & Y 좌표로 레이아웃 업데이트
                windowManager.updateViewLayout(floatingView, mParams);
            }

            public void onFinish() {
                mParams.x = 0;

                // 새로운 X & Y 좌표로 레이아웃 업데이트
                windowManager.updateViewLayout(floatingView, mParams);
            }
        }.start();
    }

    /* 플로팅 위젯 보기를 오른쪽으로 이동하는 방법 */
    private void moveToRight(final int current_x_cord) {

        // 움직이는 횟수와 시간 0으로 초기화
        new CountDownTimer(0, 0) {

            // 플로팅 뷰 매개변수 제거 가져오기
            WindowManager.LayoutParams mParams = (WindowManager.LayoutParams) floatingView.getLayoutParams();

            public void onTick(long t) {
                long step = (500 - t) / 5;

                mParams.x = (int) (width + (current_x_cord * current_x_cord * step) - floatingView.getWidth());

                // 새로운 X & Y 좌표로 레이아웃 업데이트
                windowManager.updateViewLayout(floatingView, mParams);
            }

            public void onFinish() {
                mParams.x = (int) (width- floatingView.getWidth());

                // 새로운 X & Y 좌표로 레이아웃 업데이트
                windowManager.updateViewLayout(floatingView, mParams);
            }
        }.start();
    }


    // 앱이 종료될때 실행
    @Override
    public void onDestroy(){
        super.onDestroy();

        if(floatingView != null){
            windowManager.removeView(floatingView);

        }
        if (imageClose != null){

            windowManager.removeView(imageClose);
        }

    }

main.activity는 전에 위젯을 만들었던 코드와 비슷하지만 혹시라도 구동이 안될 수 있음으로 코드를 올려보았다.

코드는 다음과 같다.

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    public static final int SYSTEM_ALERT_WINDOW_PERMISSION = 7;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        // 버튼 정의
        Button button ;
        button = (Button)findViewById(R.id.buttonShow);


        // 권한을 확인한다.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
            RuntimePermissionForUser();
        }

        // 버튼 클릭
        button.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View view) {

                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                    // 권한이 설정돼있으면 위젯 액티비티로 연결
                    startService(new Intent(MainActivity.this, FloatingWidgetShowService.class));
                    finish();
                }
                else if (Settings.canDrawOverlays(MainActivity.this)) {
                    // 권한이 설정돼있으면 위젯 액티비티로 연결
                    startService(new Intent(MainActivity.this, FloatingWidgetShowService.class));
                    finish();
                }
                else {
                    RuntimePermissionForUser();

                    Toast.makeText(MainActivity.this, "System Alert Window Permission Is Required For Floating Widget.", Toast.LENGTH_LONG).show();
                }
            }
        });

    }

    // M 버전(안드로이드 6.0 마시멜로우 버전) 보다 같거나 큰 API에서만 설정창 이동 가능
    public void RuntimePermissionForUser() {
        Intent PermissionIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
        startActivityForResult(PermissionIntent, SYSTEM_ALERT_WINDOW_PERMISSION);
    }
}

여기서 바뀐 게 제일 많은 위젯 xml 코드를 보면 다음과 같다.

<?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="wrap_content"
    android:layout_height="wrap_content"
    tools:context="FloatingWidgetShowService">

    <RelativeLayout
        android:id="@+id/MainParentRelativeLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:ignore="UselessParent">

        <!-- This layout is the Collapsed layout -->
        <RelativeLayout
            android:id="@+id/Layout_Collapsed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:visibility="visible">
            <ImageView
                android:id="@+id/Logo_Icon"
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:layout_marginTop="10dp"
                android:src="@drawable/pin" />

        </RelativeLayout>

        <!-- 이 레이아웃은 확장 레이아웃입니다-->

        <LinearLayout
            android:id="@+id/Layout_Expended"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#FFF3E0"
            android:gravity="center"

            android:orientation="horizontal"
            android:padding="8dp"
            android:visibility="gone">

            <ImageView
                android:id="@+id/WebsiteLogoIcon"
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:src="@drawable/pin"
                tools:ignore="ContentDescription" />
            <LinearLayout
                android:id="@+id/LinearLayout_2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:paddingLeft="5dp"
                    android:paddingRight="5dp"
                    android:paddingTop="5dp"
                    android:text="Android-Examples"
                    android:textAlignment="center"
                    android:textSize="18dp"
                    android:textColor="#000"
                    android:textStyle="bold" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="www.android-examples.com"
                    android:textAlignment="center"
                    android:textSize="13dp"
                    android:textColor="#000"
                    android:textStyle="bold" />
            </LinearLayout>

            <ImageView
                android:id="@+id/Widget_expand_Icon"
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:layout_weight="1"
                android:padding="10dp"
                android:src="@drawable/expended" />
        </LinearLayout>
    </RelativeLayout>
</FrameLayout>

 코드는 클릭하기 전과 클릭 후에 모습인 두 가지 모습을 모두 다 구현했고 추가적으로 클릭하고 나서 확장된 모습에서 버튼을 삽입해 위젯을 다양한 방법으로 응용해보았다.

코드를 수정했다면 run을 눌러 잘 실행되는지 확인해본다.

위 코드를 실행하게 되면 다음 출력 화면과 같은 결과를 얻을 수 있다.

<결과화면>

참고

 

Android Create Floating Widget To Show Over Apps Menu Tutorial - Android Examples

Floating Widget is a very useful part of a android application. Floating widget can display over apps menu and mobile phone screen and user can itself close if or open it. So in this tutorial we would going to create an Floating Widget which can show our a

www.android-examples.com

 

 

Android Floating Widget like Facebook Messenger Chat Head - Androhub

Floating widgets are the views that float over the screen. We all love the chat heads (or chat bubbles) from the popular Facebook Messenger. This provides very handy and easy access to the chat conversation screen no matter on which screen you are. Chat he

www.androhub.com

github

 

junjange/Java-Learning

자바 학습. Contribute to junjange/Java-Learning development by creating an account on GitHub.

github.com