상세 컨텐츠

본문 제목

안드로이드 화면 뷰어 터치 스크롤 컨트롤 관련

스마트기기개발관련/안드로이드 개발

by AlrepondTech 2020. 9. 22. 18:31

본문

반응형

 

 

 

 

=================================

=================================

=================================

 

 

 

 

 

 

작성: 202psj.tistory.com

 

스크롤 뷰 컨트롤 작성시 터치부분 스크롤 주의 사항

이 두 api 차이가 있으므로 뷰어에 따른 상대좌표 절대좌표 표시가 다르므로 꼭 구분해서 사용하자.

터치 스크롤 뷰에는 event.getRawX();  를 많이 사용하는 편이다.  또한 특정뷰어에 대한 좌표를 구할때

event.getX()또 사용해서 구하는게 또 유리하게 작용할때두 있다.

 

event.getRawX(); 

event.getX();

 

밑내용 발췌

getRawX() and getRawY(): 화면의 절대 좌표를 제공한다. 화면 좌표를 알 수 있는 getX(), getY() 는 사용하는 방법에 따라 절대 좌표나 상대좌표를 제공한다. 주의해야 한다.

 

 

//----------------------------------------------------------------------

출처: http://gtko.tistory.com/205

Large Image Scrolling Using Low Level Touch Events
from:http://www.anddev.org/large_image_scrolling_using_low_level_touch_events-t11182.html

Description:
메모리에 적재된 이미지의 일정 부분이 윈도우에 보이는 것을 상상해 보자. 이 위도우는 디스플레이와 동일한 크기이다. 이 윈도우에 있는 이미지의 표면에 터치 이벤트가 발생하는 것을 어떻게 사용할 수 있을까? 이를 수행하기 위해서는:

  • 커다란 이미지를 메모리에 적재한다.
  • 우리 디스플레이와 동일한 크기의 스크롤 가능한 rectangle을 설정한다.
  • touch event로 이 스크롤 rectangle을 움직이자.
  • 이 scroll rectangle 사이의 이미지 부분을 그린다.


메모리 보다 커다란 이미지를 보이게 하려면 어떻게 하나?
여기서 이야기하는 이미지는 메모리에 적재 가능한 크기이다. 만약 메모리 보다 커다란 이미지를 적재하고자 하면 streaming/cache (웹 같은 곳에서) 솔루션이나 compression/decompressiont (local file)을 적용해야 한다.


GestureDetector 와 GestureBuilder 가 무엇인지?
GestureDetector는 flinglong-press or double-tap 같은 동작을 처리하기 좋다. GestureDetector를 사용하기 어려운 동작이나 제공하지 않는 동작은 GestureBuilder로 구성할 수 있다.


구현
0) 새로운 안드로이드 프로젝트를
android 2.0 이상
main activity LargeImageScroller
package name com.example.largeimagescroller


1) 이미지 확보
1440x1080 크기의 이미지.
* 너무 큰 이미지를 사용하면 ran out of memory 를 만날 수 있다.
이 이미지를 
testlargeimg.png 이란 이름으로  /drawable-hdpi 에 위치시킨다. (안드로이드 버전에 따라 drawable 도 무방)

2) 
AndroidManifest.xml 

  1. <application ...
  2.   android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
  3.   android:debuggable="true"
  4.   <Activity ...
  5.      android:screenOrientation="landscape"

1. 전체 화면 모드와 가로 모드로 설정
2. 실제 디바이스에서 디버깅
5. 가로 모드

 

3) LargeImageScroller Activity
SampleView 를 Custom view로 설정한다.

  1. public class LargeImageScroller extends Activity {
  2.  
  3.         /** Called when the activity is first created. */
  4.         @Override
  5.         public void onCreate(Bundle savedInstanceState) {
  6.                 super.onCreate(savedInstanceState);
  7.  
  8.                 setContentView(new SampleView(this));
  9.         }
  10.        
  11.         private static class SampleView extends View {
  12.  
  13.                 public SampleView(Context context) {
  14.                         super(context);
  15.                 }
  16.                
  17.                 @Override
  18.                 public boolean onTouchEvent(MotionEvent event) {
  19.                 }
  20.  
  21.                 @Override
  22.                 protected void onDraw(Canvas canvas) {
  23.                 }
  24.         }
  25. }


디스플레이 크기를 알기 위해

  1. Display display = ((WindowManager)
  2.                         getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
  3.  
  4. displayWidth = display.getWidth();             
  5. displayHeight = display.getHeight();

getDefaultDisplay() 가 항상 같은 화면 크기를 돌려주는 것은 아니다. 즉, 화면의 orientation에 따라 값이 달라 진다.
만약 orientaion이 변경되는 상황을 알고 싶으면 onSizeChanged() API 로 hook 하면 된다.


4) SampleView 작성
    ㄱ) 생성자

  1. displayRect = new Rect(0, 0, displayWidth, displayHeight);
  2. scrollRect = new Rect(0, 0, displayWidth, displayHeight);
  3.  
  4. bmLargeImage = BitmapFactory.decodeResource(getResources(),
  5.                                 R.drawable.testlargeimg);

scroll rectangle과 display rectangle을 일치시키므로서 앱을 실행하고 이미지의 좌-상 부분이 화면에 맞게 표시될 것이다. 
We have initialized our scroll rectangle to be exactly the same coordinates as the display rectangle. When we first run our application this means the upper-leftmost portion of our image will be visible.



   ㄴ) Touch event handler:
터치 이벤트를 down, up, move 의 동작으로 처리한다.

  1. @Override
  2. public boolean onTouchEvent(MotionEvent event) {
  3.  
  4.         switch (event.getAction()) {
  5.                 case MotionEvent.ACTION_DOWN:
  6.                         startX = event.getRawX();
  7.                         startY = event.getRawY();
  8.                         break;
  9.  
  10.                 case MotionEvent.ACTION_MOVE:
  11.                         float x = event.getRawX();
  12.                         float y = event.getRawY();
  13.                         scrollByX = x - startX;
  14.                         scrollByY = y - startY;
  15.                         startX = x;
  16.                         startY = y;
  17.                         invalidate();
  18.                         break;
  19.                 }
  20.                 return true;
  21.         }
  22. }

처음 화면에 터치를 하면 ACTION_DOWN 가 발생하는데 이후 손가락을 이동하면 ACTION_MOVE 이벤트가 이어진다. 이벤트가 발생한 좌표는 getRawX() and getRawY()로 확인 가능하다.
getRawX() and getRawY(): 화면의 절대 좌표를 제공한다. 화면 좌표를 알 수 있는 getX(), getY() 는 사용하는 방법에 따라 절대 좌표나 상대좌표를 제공한다. 주의해야 한다.

연속적인 ACTION_MOVE 를 scroll rectangle 에 적용하고 있다. 이 과정은 몇번씩 반복되어 이미지를 부드럽게 스크롤이 되게 합니다.
scrollByX and scrollByY 는 이미지가 그려지기에 필요한 스크롤의 총량을 보관하고 있다.
startX and startY 는 MOVE 의 움직임에 대한 값을 가진다.
ACTION_MOVE 이벤트는 많이 발생하는데 event handler 에서 실행되는 것을 최소화 시키는게 좋다. 어떤 이벤트 핸들러라도 마찬가지다.

Given that the ACTION_MOVE event may get generated many times, it is best to keep the code that executes from within the event handlerto a minimum. This is true for any event handler

Invalidate(): OS 에게 우리 뷰를 redraw 해준다. redraw는onDraw() (discussed below), where we update the coordinates of the scroll rectangle and repaint the enclosed bitmap portion

return true: OS에게 이벤트 처리를 여기서 완료하고 이 이벤트를 더 이상 다른 뷰등에 전달하지 않게 한다.


Draw updater:

onDraw()는 갱신된 scroll rectangle 좌표 계산과 bitmap 영역을 갱신된 좌표 rectangle 에 새롭게 그려야 한다.
화면에 터치한 손가락을 왼쪽으로 이동하면 scroll rectangle 아래에서 bitmap 이미지가 왼쪽으로 당겨질 것으로 생각한다 - 이것은 scroll rectangle이 오른쪽으로 이동하는 것을 의미한다.
즉 ACTION_MOVE event handler에서 이미지가 왼쪽으로 움직이는 것은 scroll rectangle이 오른쪽으로 움직이는 것이다. .

  1. int newScrollRectX = scrollRectX - (int)scrollByX;
  2. int newScrollRectY = scrollRectY - (int)scrollByY;

이미지가 화면을 벗어나지 않게 정밀하게 ...

  1. // Don't scroll off the left or right edges of the bitmap.
  2. if (newScrollRectX < 0)
  3.         newScrollRectX = 0;
  4. else if (newScrollRectX > (bmLargeImage.getWidth() - displayWidth))
  5.         newScrollRectX = (bmLargeImage.getWidth() - displayWidth);
  6.  
  7. // Don't scroll off the top or bottom edges of the bitmap.
  8. if (newScrollRectY < 0)
  9.         newScrollRectY = 0;
  10. else if (newScrollRectY > (bmLargeImage.getHeight() - displayHeight))
  11.         newScrollRectY = (bmLargeImage.getHeight() - displayHeight);

좌표 0 에 대한 검사: our left (or top) coordinate is 0 for both the scroll rectangle and the bitmap,

오른쪽에 대한 검사는 아래 그림을 보면 이해가 쉽다.

 


이제 계산된 그림을 그리는 부분이 남았다...

  1. scrollRect.set(newScrollRectX, newScrollRectY, newScrollRectX +displayWidth,
  2.                                         newScrollRectY +displayHeight);
  3. Paint paint = new Paint();
  4. canvas.drawBitmap(bmLargeImage, scrollRect, displayRect, paint);


마지막으로 scroll 좌표를 새로운 좌표로 변경해 준다.,

  1. scrollRectX = newScrollRectX;
  2. scrollRectY = newScrollRectY;


5) 빌드


One final note: 
여기서는 새로운 bitmap 을 생성하는 코드는 없다. 이벤트 처리시 새로운 bitmap을 생성하는 것은 성능을 잡아 먹는다. ACTION_MOVE events handler에서 bitmap을 새로 생성하는 대신에 메모리에 적재된 bitmap 을 새로 그리는 방법을 사용한 것이다.


Takeaways:

  • The implementation described here is for simple scrolling needs, and for use with images that will fit into memory.
  • ACTION_DOWN and ACTION_MOVE events can be used to calculate scroll move updates; you will get several-to-many ACTION_MOVE events for one move gesture.
  • To avoid poor performance, try to keep the code that executes from within theevent handlers to a minimum.
  • To avoid poor performance, don't create bitmaps on the fly (in this tutorial we only create one bitmap on startup).
  • If you need to handle different kinds of gestures (flinglong-pressdouble-tap, etc.) consider an alternative such as GestureDetector.



"/src/your_package_structure/LargeImageScroller.java"

  1. package com.example.largeimagescroller;
  2.  
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.content.Context;
  6. import android.graphics.Bitmap;
  7. import android.graphics.BitmapFactory;
  8. import android.graphics.Canvas;
  9. import android.graphics.Paint;
  10. import android.graphics.Rect;
  11. import android.view.Display;
  12. import android.view.MotionEvent;
  13. import android.view.View;
  14. import android.view.WindowManager;
  15.  
  16. public class LargeImageScroller extends Activity {
  17.  
  18.         // Physical display width and height.
  19.         private static int displayWidth = 0;
  20.         private static int displayHeight = 0;
  21.  
  22.         /** Called when the activity is first created. */
  23.         @Override
  24.         public void onCreate(Bundle savedInstanceState) {
  25.                 super.onCreate(savedInstanceState);
  26.  
  27.                 // displayWidth and displayHeight will change depending on screen
  28.                 // orientation. To get these dynamically, we should hook onSizeChanged().
  29.                 // This simple example uses only landscape mode, so it's ok to get them
  30.                 // once on startup and use those values throughout.
  31.                 Display display = ((WindowManager)
  32.                         getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
  33.                 displayWidth = display.getWidth();             
  34.                 displayHeight = display.getHeight();    
  35.  
  36.                 // SampleView constructor must be constructed last as it needs the
  37.                 // displayWidth and displayHeight we just got.
  38.                 setContentView(new SampleView(this));
  39.         }
  40.        
  41.         private static class SampleView extends View {
  42.                 private static Bitmap bmLargeImage; //bitmap large enough to be scrolled
  43.                 private static Rect displayRect = null; //rect we display to
  44.                 private Rect scrollRect = null; //rect we scroll over our bitmap with
  45.                 private int scrollRectX = 0; //current left location of scroll rect
  46.                 private int scrollRectY = 0; //current top location of scroll rect
  47.                 private float scrollByX = 0; //x amount to scroll by
  48.                 private float scrollByY = 0; //y amount to scroll by
  49.                 private float startX = 0; //track x from one ACTION_MOVE to the next
  50.                 private float startY = 0; //track y from one ACTION_MOVE to the next
  51.  
  52.                 public SampleView(Context context) {
  53.                         super(context);
  54.  
  55.                         // Destination rect for our main canvas draw. It never changes.
  56.                         displayRect = new Rect(0, 0, displayWidth, displayHeight);
  57.                         // Scroll rect: this will be used to 'scroll around' over the
  58.                         // bitmap in memory. Initialize as above.
  59.                         scrollRect = new Rect(0, 0, displayWidth, displayHeight);
  60.  
  61.                         // Load a large bitmap into an offscreen area of memory.
  62.                         bmLargeImage =BitmapFactory.decodeResource(getResources(),
  63.                                 R.drawable.testlargeimg);
  64.                 }
  65.                
  66.                 @Override
  67.                 public boolean onTouchEvent(MotionEvent event) {
  68.  
  69.                         switch (event.getAction()) {
  70.                                 case MotionEvent.ACTION_DOWN:
  71.                                         // Remember our initial down event location.
  72.                                         startX = event.getRawX();
  73.                                         startY = event.getRawY();
  74.                                         break;
  75.  
  76.                                 case MotionEvent.ACTION_MOVE:
  77.                                         float x = event.getRawX();
  78.                                         float y = event.getRawY();
  79.                                         // Calculate move update. This will happen many times
  80.                                         // during the course of a single movement gesture.
  81.                                         scrollByX = x - startX; //move update x increment
  82.                                         scrollByY = y - startY; //move update y increment
  83.                                         startX = x; //reset initial values to latest
  84.                                         startY = y;
  85.                                         invalidate(); //force a redraw
  86.                                         break;
  87.                         }
  88.                         return true; //done with this event so consume it
  89.                 }
  90.  
  91.                 @Override
  92.                 protected void onDraw(Canvas canvas) {
  93.  
  94.                         // Our move updates are calculated in ACTION_MOVE in the opposite direction
  95.                         // from how we want to move the scroll rect. Think of this as dragging to
  96.                         // the left being the same as sliding the scroll rect to the right.
  97.                         int newScrollRectX = scrollRectX -(int)scrollByX;
  98.                         int newScrollRectY = scrollRectY -(int)scrollByY;
  99.  
  100.                         // Don't scroll off the left or right edges of the bitmap.
  101.                         if (newScrollRectX < 0)
  102.                                 newScrollRectX = 0;
  103.                         else if (newScrollRectX >(bmLargeImage.getWidth() - displayWidth))
  104.                                 newScrollRectX =(bmLargeImage.getWidth() - displayWidth);
  105.  
  106.                         // Don't scroll off the top or bottom edges of the bitmap.
  107.                         if (newScrollRectY < 0)
  108.                                 newScrollRectY = 0;
  109.                         else if (newScrollRectY >(bmLargeImage.getHeight() - displayHeight))
  110.                                 newScrollRectY =(bmLargeImage.getHeight() - displayHeight);
  111.  
  112.                         // We have our updated scroll rect coordinates, set them and draw.
  113.                         scrollRect.set(newScrollRectX, newScrollRectY,
  114.                                 newScrollRectX + displayWidth, newScrollRectY + displayHeight);
  115.                         Paint paint = new Paint();
  116.                         canvas.drawBitmap(bmLargeImage, scrollRect, displayRect, paint);
  117.  
  118.                         // Reset current scroll coordinates to reflect the latest updates,
  119.                         // so we can repeat this update process.
  120.                         scrollRectX = newScrollRectX;
  121.                         scrollRectY = newScrollRectY;
  122.                 }
  123.         }
  124. }

 

 

 

=================================

=================================

=================================

 

 

 

 

출처: http://utime.blog.me/150091436416

하나의 포인터로 터치 했을 경우 상하좌우 구별 하는 셈플이다.


main.xml

 
<?xml version="1.0" encoding="utf-8"?>

    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:id="@+id/textview_state"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    />
<TextView  
    android:id="@+id/textview_action"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    />
</LinearLayout>



터치의 정보와 상태 값을 보여주기 위한 화면이다.



MotionEventTest.java

 
package com.androidstudy.MotionEventTest;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.TextView;

public class MotionEventTest extends Activity {
    
    private TextView mTxView = null;
    private TextView mTxAction = null;
    private MotionEventAction mea = null;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        mea = new MotionEventAction();
        
        mTxView = (TextView)findViewById(R.id.textview_state);
        mTxAction = (TextView)findViewById(R.id.textview_action);
    }
    
    private void setLogMsg( StringBuilder sMsg, final String sTitle, final String sState)
    {
        sMsg.append( sTitle ).append(" = ").append( sState ).append("\r\n");
    }
    
    private void setLogMsg( StringBuilder sMsg, final String sTitle, final int iState)
    {
        this.setLogMsg(sMsg, sTitle, Integer.toString(iState) );
    }
    
    private void setLogMsg( StringBuilder sMsg, final String sTitle, final float fState)
    {
        this.setLogMsg(sMsg, sTitle, Float.toString(fState) );
    }
    
    private void setLogMsg( StringBuilder sMsg, final String sTitle, final long lState)
    {
        this.setLogMsg(sMsg, sTitle, Long.toString(lState) );
    }
    
    private class MotionEventAction
    {
        private float mDownX = -1;
        private float mDownY = -1;
        private float mUpX = -1;
        private float mUpY = -1;
        
        public  float IgnoreValue = 10;
        
        public MotionEventAction() {
            // TODO Auto-generated constructor stub
        }
        
        protected void ActionLeftMoveEvent(float fGapValue)
        {
            mTxAction.setText( "ActionLeftMoveEvent = " + Float.toString(fGapValue) );
        }
        
        protected void ActionRightMoveEvent(float fGapValue)
        {
            mTxAction.setText( "ActionRightMoveEvent = " + Float.toString(fGapValue) );
        }
        
        protected void ActionUpMoveEvent(float fGapValue)
        {
            mTxAction.setText( "ActionUpMoveEvent = " + Float.toString(fGapValue) );
        }
        
        protected void ActionDownMoveEvent(float fGapValue)
        {
            mTxAction.setText( "ActionDownMoveEvent = " + Float.toString(fGapValue) );
        }
        
        private void ActionCheck()
        {
            float GapX = mDownX - mUpX;
            float GapY = mDownY - mUpY;
            float AbsGapX = Math.abs(GapX);
            float AbsGapY = Math.abs(GapY);
            
            if( (AbsGapX< IgnoreValue) && (AbsGapY<IgnoreValue))
            {
                // 이 간격은 무시.
                mTxAction.setText( "Ignore Action" );
                return;
            }
            
            if( AbsGapX < AbsGapY )
            {
                if( mDownY < mUpY )
                    ActionDownMoveEvent(AbsGapY);
                else
                    ActionUpMoveEvent(AbsGapY);
            }else
            {
                if( mDownX < mUpX )
                    ActionRightMoveEvent(AbsGapX);
                else
                    ActionLeftMoveEvent(AbsGapX);
            }
        }
        
        public void ActionMotionEvent( MotionEvent event )
        {
            switch( event.getActionMasked() )
            {
                case MotionEvent.ACTION_DOWN :
                {
                    mDownX = event.getX();
                    mDownY = event.getY();
                    break;
                }
                
                case MotionEvent.ACTION_UP :
                {
                    mUpX = event.getX();
                    mUpY = event.getY();
                    
                    ActionCheck();
                    break;
                }
            }
        }
    }
    
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        
        StringBuilder sMsg = new StringBuilder();
        
        this.setLogMsg( sMsg, "ActionMasked", event.getActionMasked() );
        this.setLogMsg( sMsg, "DeviceId    ", event.getDeviceId() );
        this.setLogMsg( sMsg, "PointerCount", event.getPointerCount() );
        this.setLogMsg( sMsg, "RawX        ", event.getRawX() );
        this.setLogMsg( sMsg, "RawY        ", event.getRawY() );
        this.setLogMsg( sMsg, "X           ", event.getX() );
        this.setLogMsg( sMsg, "Y           ", event.getY() );
        this.setLogMsg( sMsg, "DownTime    ", event.getDownTime() );
        this.setLogMsg( sMsg, "EventTime   ", event.getEventTime() );
        this.setLogMsg( sMsg, "Size        ", event.getSize() );
        sMsg.append("\r\n");
        
        mTxView.setText( sMsg.toString() );
        
        sMsg = null;
        
        mea.ActionMotionEvent(event); 
        
        return super.onTouchEvent(event);
    }
}



터치 이벤트를 받아 얼마만큼 움직였는지 차이를 구해 상하좌우를 구별한다.
각각 방향 이벤트 마다 얼마만큼 움직였는지 값을 전달하도록 돼 있다.

MotionEventAction 클래스를 상속받아 각각 방향 이벤트 함수를 Override 하여 사용하면 바람직 할 듯 하다.

[출처] android : onTouchEvent 로 상하좌우 구분하기|작성자 시간한줌

 

 

=================================

=================================

=================================

 

 

반응형


관련글 더보기

댓글 영역