728x90

Adapter 

데이터를 관리하며 데이터 원본과 어댑터뷰(ListView, GridView) 사이의 중계 역할

 

어댑터뷰는 어댑터에 정의된 인터페이스를 바탕으로 필요한 정보를 요청하여 항목뷰를 화면에 표시하거나 선택된 항목뷰를 처리한다.

 

  • 1) BaseAdapter
    • 어댑터 클래스의 공통 구현
    • 사용자 정의 어댑터 구현 시 사용
  • 2) ArrayAdapter
    • 객체 배열이나 리소스에 정의된 배열로부터 데이터를 공급받음
  • 3) CursorAdapter
    • 데이터베이스로부터 데이터를 공급받음
  • 4) SimpleAdapter
    • 데이터를 Map(키,값)의 리스트로 관리
    • 데이터를 XML파일에 정의된 뷰에 대응시키는 어댑터
     

728x90

MainActivity iniRecyclerView() 

binding.recyclerView.apply {

            adapter = productAdapter
            layoutManager = LinearLayoutManager(baseContext, LinearLayoutManager.VERTICAL, false)
            val divider =
                DividerItemDecoration(baseContext, LinearLayoutManager.VERTICAL)//divider 추가
            addItemDecoration(divider)

            //애니매이션 효과
            val fadeIn = AlphaAnimation(0f, 1f).apply { duration = 500 }
            val fadeOut = AlphaAnimation(1f, 0f).apply { duration = 500 }
            var isTop = true

            addOnScrollListener(object : RecyclerView.OnScrollListener() {
                override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                    super.onScrollStateChanged(recyclerView, newState)

                    /*
                    canScrollVertically(int direction) 메서드는 RecyclerView가 주어진 방향으로 스크롤할 수 있는지 여부를 나타냄. direction -> -1 상단, 1 하단
                    canScrollVertically(1)은 하단 스크롤할 수 있는지를 확인, canScrollVertically(-1) 상단 스크롤할 수 있는지를 확인
                    !recyclerView.canScrollVertically(-1)는 상단으로 더 이상 스크롤할 수 없다는 것, newState == RecyclerView.SCROLL_STATE_IDLE는 현재 스크롤 상태가 대기 상태인지를 확인.
                    */

                    if (!recyclerView.canScrollVertically(-1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
                        binding.apply {
                            fbScrollButton.startAnimation(fadeOut)
                            fbScrollButton.isVisible = false
                            isTop = true
                        }
                    } else {
                        if (isTop) {
                            binding.apply {
                                fbScrollButton.isVisible = true
                                fbScrollButton.startAnimation(fadeIn)
                                isTop = false
                            }
                        }
                    }

                }
            })

        }

 

 

addOnScrollListener()를 이용해서 스크롤을 감지 

 

xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_Text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="36dp"
        android:text="@string/region"
        android:textColor="@color/black"
        android:textSize="22sp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_marginStart="5dp"
        android:src="@drawable/bottom_arrow"
        app:layout_constraintBottom_toBottomOf="@id/tv_Text"
        app:layout_constraintStart_toEndOf="@id/tv_Text"
        app:layout_constraintTop_toTopOf="@id/tv_Text" />

    <ImageView
        android:id="@+id/iv_notification"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_marginTop="36dp"
        android:layout_marginEnd="18dp"
        android:src="@drawable/notification"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_Text" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fb_ScrollButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="36dp"
        android:backgroundTint="@color/white"
        android:clickable="true"
        android:elevation="0dp"
        android:hapticFeedbackEnabled="true"
        android:src="@drawable/top_arrow"
        android:tint="@null"
        android:visibility="invisible"
        app:borderWidth="0dp"
        app:fabCustomSize="40dp"
        app:fabSize="normal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:maxImageSize="40dp"
        app:rippleColor="#2962FF" />

</androidx.constraintlayout.widget.ConstraintLayout>
        
        
        ///xml///

 

구현화면

728x90
  • 뷰 바인딩 기능을 사용하면 뷰와 상호작용하는 코드를 쉽게 작성할 수 있다.
  • 모듈에서 사용 설정된 뷰 바인딩은 모듈에 있는 각 XML 레이아웃 파일의 결합 클래스를 생성한다.
  • 바인딩 클래스의 인스턴스에는 상응하는 레이아웃에 ID가 있는 모든 뷰의 직접 참조가 포함된다.
  • 대부분의 경우 뷰 바인딩이 findViewById를 대체한다.

findViewById와의 차이점

 

1) Null 안전성 (Null Safety)

뷰 바인딩 기능을 사용하면, 앱이 레이아웃의 각 뷰를 직접 참조할 수 있게 해주는 안전한 코드를 자동으로 생성한다. 이것은 뷰를 사용할 때 'null' 값으로 인한 오류, 즉 뷰가 아직 화면에 나타나지 않았는데 그 뷰를 사용하려고 할 때 생길 수 있는 문제들을 예방해 준다.

예를 들어, 만약 레이아웃에 버튼이 있어야 하는데 아직 버튼이 생성되지 않았다면, 뷰 바인딩은 이를 안전하게 처리하여 앱이 충돌하지 않도록 한다. 또한, 만약 레이아웃의 일부만 뷰가 있다면, 뷰 바인딩은 해당 뷰가 '가능성 있는 null'(Nullable)임을 알려주어, 개발자가 더 주의 깊게 코드를 작성하도록 돕는다.

2) 타입 안전성 (Type Safety)

XML 레이아웃 파일에서 정의된 뷰의 타입과 자동 생성된 바인딩 클래스의 필드 타입이 항상 일치하기 때문에, 타입이 서로 맞지 않아 발생할 수 있는 오류를 방지한다.

예를 들어, 이미지 뷰(ImageView)에 텍스트를 설정하려고 하면 오류가 발생할 텐데, 뷰 바인딩을 사용하면 이런 실수를 할 가능성이 없어진다. 즉, 이미지 뷰는 이미지 뷰로, 텍스트 뷰는 텍스트 뷰로만 사용되게 하여, 잘못된 타입 사용으로 인한 오류가 발생하지 않도록 보장한다.

 

Gradle 설정

android{
	...
    
    // AndroidStudio 3.6 ~ 4.0
    viewBinding{
    	enabled = true
    }
    
    // AndroidStudio 4.0 ~
    buildFeatures{
    	viewBinding = true
    }
}

 

 

뷰 바인딩(View Binding) 기능을 사용할 때, 안드로이드 스튜디오는 레이아웃 파일의 이름을 기반으로 한 바인딩 클래스를 자동으로 생성한다. 이 클래스는 레이아웃에 있는 모든 뷰에 대한 참조를 포함하며, 이를 통해 코드에서 직접 뷰에 접근할 수 있게 해준다. 바인딩 객체의 이름은 레이아웃 파일 이름에 'Binding'을 붙여 만들어진다.

예를 들어:

  • 레이아웃 파일 이름이 **activity_main.xml**인 경우, 생성되는 바인딩 클래스의 이름은 **ActivityMainBinding**이 된다.
  • 레이아웃 파일 이름이 **fragment_home.xml**인 경우, 생성되는 바인딩 클래스의 이름은 **FragmentHomeBinding**이 된다.

 

728x90

ellipsize 옵션으로 글자가 maxline 옵션설정값을 넘어가면 ... 으로 보이는 것을 더보기 / 접기 로 구현  

private fun setViewMore(
        contentTextView: TextView,
        viewMoreTextView: TextView,
        closeTextView: TextView
    ) {
        // getEllipsisCount()을 통한 더보기 표시 및 구현
        contentTextView.post {
            val lineCount = contentTextView.layout.lineCount
            if (lineCount > 0) {
                if (contentTextView.layout.getEllipsisCount(lineCount - 1) > 0) {
                    // 더보기 표시
                    viewMoreTextView.isVisible = true

                    // 더보기, 접기 클릭 이벤트
                    viewMoreTextView.setOnClickListener {
                        contentTextView.maxLines = Int.MAX_VALUE
                        viewMoreTextView.isVisible = false
                        closeTextView.isVisible = true
                    }
                    // 접기 표시
                    closeTextView.setOnClickListener {
                        contentTextView.maxLines = 1
                        viewMoreTextView.isVisible = true
                        closeTextView.isVisible = false
                    }
                }
            }
        }
    }

 

 

728x90

SNS 앱 만들기 화면 구성
1.메인 페이지 2. 로그인 페이지 3. 회원가입 페이지 4. 콘텐츠 디테일 페이지 5. 마이 페이지

 

기능

  • 메인 페이지에서 로그인 페이지, 디테일 페이지, 마이 페이지로 이동 할 수 있게 구현
  • 회원가입 페이지에서 Email 형식이 맞지 않거나 Email 이 중복되면 가입 승인이 안되도록 구현
  • 디테일 페이지에서 사용자와 상호작용을 위해 설명 더보기/접기 기능 좋아요 표시, 댓글 입력기능 구현
  • 마이 페이지에서 이름, 자기소개, MBIT, 취미 등 편집기능, 이미지를 눌렀을 때 이미지를 선택해서 바꾸는 기능 구현
  • 다국어지원(영어), 가로모드, 다크테마 등 구현 

필수 구현 기능
[v] 메인 화면
[v] 디테일 화면
[v] 로그인, 회원가입 화면
[v] 마이 페이지 화면
[v] Activity 전환시 animation 구현
[v] 영어 버전으로 변경 적용해보기(string.xml)


추가 구현 기능(선택)
[v] 동그란 ImageView 만들기
[v] 스크롤 기능 추가
[v] 더보기 기능
[ x ] Font 크기 설정에 따라 글씨 크기 달라지도록 구현
[v] Dark theme 구현
[v] 세로/가로 모드 ui 분리 구현
[ x ] 회원 정보 관리 구현

 

구현화면 

가로모드
다크모드 적

 

다국어지원(영어)

 

로그인처리

 

 

마이페이지
디테일 페이지

 

 

마지막 날 어려운점 

내가 짠 코드가 아닌 코드를 보면서 버그를 고치는데 애먹었음 흐름을 보면서 간신히 해결 함 

 

후기 - 첫 팀 협업....  협업 참 어렵다. 소통을 그렇게 했는데도 엇나가는 부분이 계속 생기고 수정하고 반복인 것 같다. git과 살짝 친해진 느낌도 있고 재미.. 있는 것 같기도 하고 아닌것 같기도 하고?

남이 봤을 때 읽기 쉬운 코드를 짜야한다는 말을 무슨 말인지 체감을 했고, 필요성을 많이 느낄 수 있었다. 

728x90

셋째 날 변경점 

  • 이미지 받아오기
  • id 받아오기
  • 나머지 컨텐츠 작업 

 

셋째 날 프로젝트 80% 정도 완성 

어려운점 

첫 날, 둘째 날과 마찬가지로 git에 문제가 종종있지만 금방 해결했음, 문제는 커밋컨벤션이 잘 지켜지지 않는문제와 변수나 text등 네이밍 작업이 어려움 또, 다른 사람이 작성한 코드를 읽고 내 코드에 적용을 시켜야 하는 작업에 어려움이 있음 다른 사람의 코드가 바뀌어서 내가 작성한 코드가 동작하지 않는 문제가 있어서 해결하느라 살짝 애먹음 

다음 프로젝트는 git 전략, git organization 커밋컨벤션 등 공부를 더 해야겠음 

728x90

변경점 

  • 좋아요 (하트버튼) 추가  
  • 댓글 입력방식 변경 
    • dialog 와 EditText 에서 EditText 와 ImageView를 이용한 입력창으로 변경  
    • 댓글 아이콘을 누르면 댓글입력창이 보이고 안보이고 조절 
    • 좋아요, 댓글 저장

 

메인액티비티 에서 정보를 받아옴, 아직 로그인페이지에서 오는 id를 받기 전

댓글 입력방식 변경

 

둘째 날 어려운점 

다들 깃허브가 익숙하지 않아서 커밋을 하고, 푸시를 하고 병합하는 과정에서 문제들이 생기는 팀원이 있었다. 

 

728x90

4조 팀프로젝트 SNS 만들기

  1. 프로젝트 개요
    1. 팀 명 : 커피사죠
      1. 프로젝트 인원 : (팀장) 이 * // (팀원) 김** // (팀원) 유** // (팀원) 박** (4명)
    2. 프로젝트 명 : SNS 만들기
    3. 프로젝트 기능
      1. 메인 페이지 ( MainPageActivity ) : 담당자 박**
        1. ****필수 구현 기능들에 대한 진입점을 제공
      2. 디테일 페이지 ( DetailPageActivity ) : 담당자 이 *
        1. 사용자가 메인 페이지에서 선택한 아이템의 상세 정보를 제공하는 페이지
      3. 로그인, 회원 가입 페이지 ( SignInActivity, SignUpActivity ) : 담당자 유 * *
        1. 비밀번호 입력 시, 보안을 유지하기 위해 ***로 표현
        2. 로그인 이후에는 사용자 이름이 화면에서 보이도록 구성
        3. 로그인, 회원 가입 예외 처리
        4. 예외 발생 시 사용자에게 안내 메시지를 표시
        5. 다음 동작을 안내
      4. 마이 페이지 ( MyPageActivity ) : 담당자 김 * * 
        1. 사용자 정보와 관련된 기능들을 보여주는 페이지
      5. Activity 전환시 animation 구현 : 담당자 박 * *
        1. 화면을 전환할 때 UI가 자연스럽게 전환
        2. startActivity()를 호출한 후에 overridePendingTransition() 메서드를 활용
      6. 영어 버전으로 변경 적용해보기(string.xml) 공동
        1. Multi-Language 지원을 위해 string.xml을 통해 문자열을 관리
    4. 프로젝트 일정
      1. 23/12/26 ~ 24/01/01 PM 6 : 00
    5. 구현 체크리스트
      •  i - 1 : 필수 구현 기능들에 대한 진입점을 제공
      • ii - 1 : 사용자가 메인 페이지에서 선택한 아이템의 상세 정보를 제공하는 페이지
      • iii - 1 : 비밀번호 입력 시, 보안을 유지하기 위해 ***로 표현
      • iii - 2 : 로그인 이후에는 사용자 이름이 화면에서 보이도록 구성
      • iii - 3 : 로그인, 회원 가입 예외 처리
      • iii - 4 : 예외 발생 시 사용자에게 안내 메시지를 표시
      • iii - 5 : 다음 동작을 안내
      • iiii - 1 : 사용자 정보와 관련된 기능들을 보여주는 페이지
      • V- 1 : startActivity()를 호출한 후에 overridePendingTransition() 메서드를 활용
      • Vi- 1 : Multi-Language 지원을 위해 string.xml을 통해 문자열을 관리
     

와이어프레임
메인액티비
디테일 액티비티
마이페이지 액티비티
로그인 액티비티
회원가입 액티비티

 

 

디테일 액티비티 구현 // 더보기, 접기, 댓글 남기기 구현 

 

첫 날 어려운점 

1. 각자 요구사항에 대한 이해가 다름 

2. 피그마 사용경험 없음

3. 어디서부터 어디까지 협의를 하고 프로젝트를 시작해야 하는지 어려움이 있음 

4. gradle, Android Studio 버전이 각자 다른 문제   

 

728x90

Fragment FragmentActivit 내의 어떤 동작 또는 사용자 인터페이스의 일부를 나타냅니다. 여러 개의 프래그먼트를 하나의 액티비티에 결합하여 창이 여러 개인 UI를 빌드할 수 있으며, 하나의 프래그먼트를 여러 액티비티에서 재사용할 수 있습니다. 프래그먼트는 액티비티의 모듈식 섹션이라고 생각하면 됩니다. 이는 자체적인 수명 주기를 가지고, 자체 입력 이벤트를 수신하고, 액티비티 실행 중에 추가 및 삭제가 가능합니다(다른 액티비티에 재사용할 수 있는 "하위 액티비티"와 같은 개념).

 

프래그먼트 및 프래그먼트 관리자

프래그먼트는 인스턴스화되면 INITIALIZED 상태에서 시작됩니다. 프래그먼트가 프래그먼트의 나머지 수명 주기를 전환하도록 하려면 프래그먼트를 FragmentManager에 추가해야 합니다. FragmentManager가 프래그먼트가 어떤 상태여야 하는지 확인한 다음 그 상태로 전환하는 일을 담당합니다.

프래그먼트 수명 주기를 벗어나면 FragmentManager는 프래그먼트를 호스트 활동에 연결하고 프래그먼트가 더 이상 사용되지 않을 때는 프래그먼트를 분리하는 작업도 합니다. Fragment 클래스에는 두 가지 콜백 메서드 onAttach()와 onDetach()가 있습니다. 이러한 메서드는 관련 이벤트 중 하나가 발생했을 때 작업을 위해 재정의할 수 있습니다.

onAttach() 콜백은 프래그먼트가 FragmentManager에 추가되었을 때 호스트 활동에 연결되면 호출됩니다. 이 시점에 프래그먼트는 활성 상태이며 FragmentManager가 수명 주기 상태를 관리하고 있습니다. 이 시점에서 findFragmentById() 같은 FragmentManager 메서드가 이 프래그먼트를 반환합니다.

onAttach()는 항상 수명 주기 상태 변경 전에 호출됩니다.

onDetach() 콜백은 프래그먼트가 FragmentManager에서 삭제되었을 때 호스트 활동에서 분리되면 호출됩니다. 프래그먼트가 더 이상 활성 상태가 아니므로 findFragmentById()를 사용하여 더 이상 검색할 수 없습니다.

onDetach()는 수명 주기 상태 변경 후에 항상 호출됩니다.

Fragment Lifecycle(프래그먼트 생명주기)

출처 - https://developer.android.com/guide/fragments/lifecycle?hl=ko

 

 

 

 

 

콜백 

onAttach() 

  • 프래그먼트가 액티비티에 붙여질 때 호출된다. 
    • 인자로 Context가 주어진다.

onCreated()

  • 프래그먼트가 생성될 때 호출된다. 
    • FragmentManager에 add될 때 호출된다.
    • Bunddle로 액티비티로부터의 데이터가 넘어온다. UI 초기화 불가능

onCreateView()

  • onCreated() 이후 바로 호출된다.
    • 레이아웃 inflate
    • savedInstanceState로 이전 상태에 대한 데이터 제공 
    • View와 관련된 객체를 초기화 할 수 있음

onViewCreated() 

  • onCreateView()가 정상적으로 호출되면 리턴되는 View 객체를 받아 호출된다. 
    • 이때 프래그먼트 데이터를 초기화( View의 초기값 설정 )
    • onCreateView()를 통해 반환된 View 객체는 onViewCreated()의 파라미터로 전달
    • Lifecycle이 INITIALIZED 상태로 업데이트
    • LiveData 옵저빙, RecyclerView, ViewPager2에 사용될 Adapter 세팅은 이 메소드에서 해주는 것이 적절함

onViewStateRestored()

  • 저장되어 있던 모튼 값이 복원되었을 경우 호출된다.
    • View lifecycle : INITIALIZED → CREATED 변경

onStart()

  • 액티비티의 onStart()와 유사, 사용자에게 보여질 수 있을 때 호출된다. 
    • View lifecycle : CREATED → STARTED 변경

onResume()

  • 사용자와 상호작용이 가능한 상태일 때 호출된다. 
    • Fragment가 보이는 상태에서 모든 Animator와 Transition 효과가 종료되고, 프래그먼트와 사용자가 상호작용 할 수 있을 때 onResume Callback

onPause()

  • 사용자가 프래그먼트를 떠날 때 호출된다.

onStop()

  • 프래그먼트가 화면에 보이지 않을 때 호출된다.
    • 부모 액티비티, 프래그먼트가 중단될 때, 상태가 저장될 때 호출
    • lifecylce : STARTED -> CREATED
    • FragmentTransaction을 안전하게 수행하는 마지막 지점

onSaveInstanceState()

  • 임시로 정보를 저장할 때 호출된다. 

onDestroyView()

  •  모든 animation, transaction이 완료, 프래그먼트와 관련된 뷰가 제거될 때 호출된다. // 화면으로부터 벗어났을 때 
    • Fragment View에 대한 모든 참조가 제거되어야 함 

onDestroy() 

  • 프래그먼트가 완전히 제거되거나, FragmentManager가 destroy 됐을 경우에 호출된다.    
    • Fragment Lifecycle의 끝을 알림

onDetach() 

  • 프래그먼트가 액티비티로부터 해제되어질 때 호출된다. //떨어질 때 호출 

 

Activity 생명주기

 

Activity 생명주기

Acitivty 의 생명주기를 알아야 하는 이유? - 앱의 완성도와 안정성을 높이기 위해 반드시 알아야 함 다른 앱으로 전환 시, 비정상 종료 되는 문제 사용자가 앱을 사용하지 않는데, 시스템 리소스가

dlho1222.tistory.com

 

 

 

 

 

 

 

참고 - https://developer.android.com/guide/fragments/lifecycle?hl=ko

728x90

눌렸을 때 색과 이미지가 바뀌는 버튼

 

button_selector.xml

 

image_selector.xml

 

activity_main.xml 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/layout"
        android:background="@drawable/button_selector"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:id="@+id/iv_Image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/image_selector"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/tv_Text"
            app:layout_constraintHorizontal_chainStyle="packed"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginEnd="20dp"
            />

        <TextView
            android:id="@+id/tv_Text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="버튼"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/iv_Image"
            app:layout_constraintTop_toTopOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>



</androidx.constraintlayout.widget.ConstraintLayout>

 

 

 

 

여러가지 버튼

 

button_1.xml 

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" //모양
    >
    <solid android:color="@color/white" /> //배경색
    <corners android:radius="24dp" /> //radius
    <stroke android:color="@color/black" android:width="1dp"/> //테두리 색 

</shape>

 

+ Recent posts