반응형

우리가 앱을 이용할 때 화면이 스크롤 되는 것을 자주 볼 수 있다.

이는 화면의 많은 데이터들을 효율적으로 볼 수 있게 하는 중요한 기능이라고 할 수 있다.

 

그냥 xml파일에다가 스크롤뷰만 추가하면 된다.

 

ScrollView : 기본적으로 수직방향

HorizontalScrollView : 수평방향

 

1
2
3
4
5
6
7
<ScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
        
        
    </ScrollView>
cs

 

하지만 주의할 사항이 있다.

스크롤뷰 안에는 무조건 하나의 자식(뷰, 위젯)만 들어가 있어야한다.

그렇기 때문에 스크롤뷰안에는 하나의 큰 레이아웃으로 감싸서

그 안에 다른 레이아웃이나 위젯들을 넣어야한다.

 

정말 간단하지만 중요한 스크롤뷰 기능을 알아보았다.

 

피드백은 언제나 환영입니다.

 

 

반응형
반응형

(210518 수정)

 

보통 상하좌우 어디서든 나타나게 할 수 있는 바로가기 메뉴처럼

데이터들을 화면에 좀 더 효율적이고 동적으로 UI를 보여줘야 할 때가 있다.

 

이번에 알아볼 페이지 슬라이딩(Page Sliding)은

버튼을 눌렀을 때 보이지 않던 뷰가 슬라이딩 방식으로 미끄러지면서 나타나는 기능으로써

여러 뷰를 전환하면서 보여주는 프레임 레이아웃(Frame Layout) 방식에

애니메이션(Animation) 기능을 합친 것이다.

 

간단하기 때문에 쉽게 기능을 구현할 수 있다.

 

----------------------------------------------------------------------------------------------------

 

먼저 메인 레이아웃과 슬라이딩 레이아웃을 보여줄 activity_main.xml 파일부터 보겠다.

 

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#ff11ff11" >
        <!-- 첫 번째 레이아웃은 메인 레이아웃,
        background 색상은 본인 자유 -->
 
        <TextView
            android:layout_width="184dp"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="메인"
            android:textColor="#000000"
            android:textSize="60sp" />
        <!-- 메인 레이아웃에서 "메인"이라는
        텍스트를 표시해줌-->
 
    </LinearLayout>
 
    <LinearLayout
        android:id="@+id/sliding"
        android:layout_width="200dp"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:layout_gravity="right"
        android:background="#ffffff99"
        android:visibility="invisible">
        <!-- 두 번째 레이아웃은 슬라이딩으로 보여질 레이아웃,
        여기서 visibility가 가장 중요한데 평소에 안보였다가(invisible)
        오픈 버튼을 누르면 보이게(visible)할것임 -->
 
        <TextView
            android:layout_width="172dp"
            android:layout_height="match_parent"
            android:text="슬라이딩"
            android:textColor="#000000"
            android:textSize="40sp"
            android:gravity="center"/>
        <!-- 슬라이딩 레이아웃에서 "슬라이딩" 이라는
        텍스트를 표시함 -->
 
    </LinearLayout>
 
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="right|bottom" >
        <!-- 마지막 레이아웃은 버튼이 들어있는 레이아웃,
        버튼 위치는 layout_gravityfh로 설정할 수 있는데, 
        본인 자유이고 여기서는 오른쪽과 아래를 둘 다 사용함 -->
 
        <Button
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="열기"
            android:textSize="50dp"
            android:layout_margin="20dp"/>
        <!-- 열기라는 버튼을 만들어서 누르면 슬라이딩
        레이아웃이 보이게함-->
 
    </LinearLayout>
</FrameLayout>
cs

 

이렇게 레이아웃들을 만들었고

다음으로는 슬라이딩 레이아웃이 움직이는 애니메이션 동작 정의를 할 것이다.

 

app밑에 res폴더 안에 anim 폴더를 만들고 

그 안에 sliding_left.xml과 sliding_right.xml 파일을 생성한다.

 

sliding_left.xml

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
 
    <translate
        android:fromXDelta="100%p"
        android:toXDelta="0%p"
        android:duration="500" />
</set>
cs

 sliding_right.xml 

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
 
    <translate
        android:fromXDelta="0%p"
        android:toXDelta="100%p"
        android:duration="500" />
</set>
cs

 

여기서 <translate> 태그는 슬라이딩 레이아웃이 

왼쪽이나 오른쪽으로 이동하는 애니메이션을 정의한다.

 

fromXDelta 는 어디로부터 이동하냐 라는 말이라서

left파일 같은 경우에는 끝인 100%p 이고,

right파일 같은 경우에는 처음인 0%p 이다.

 

toXDelta 는 어디까지 이동하냐 라는 말이라서

left파일 같은 경우에는 처음인 0%p 이고,

right파일 같은 경우에는 끝인 100%p 이다.

 

duration은 동작하는 시간을 말하는데 1/1000초 단위이다. (500이면 0.5초)

 

UI를 했다면 이제는 마지막으로 동작시킬 코드가 필요하다.

 

MainActivity.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class MainActivity extends AppCompatActivity {
 
  LinearLayout sliding; // 애니메이션을 동작시킬 객체
   Animation slidingLeft; // 슬라이딩이 왼쪽으로 펼쳐지는 애니메이션 객체
   Animation slidingRight; // 슬라이딩이 오른쪽으로 접어지는 애니메이션 객체
  Button btn;
  boolean isOpen = false// 페이지가 처음에는 오픈이 안 된 상태
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
       sliding = (LinearLayout) findViewById(R.id.sliding);
       slidingLeft= AnimationUtils.loadAnimation(this, R.anim.sliding_left);
       slidingRight= AnimationUtils.loadAnimation(this, R.anim.sliding_right);
 
       SlidingAnimationListener listener = new SlidingAnimationListener();
       slidingLeft.setAnimationListener(listener);
       slidingRight.setAnimationListener(listener);
 
       btn = (Button)findViewById(R.id.btn);
       btn.setOnClickListener(new View.OnClickListener() { // 열기 버튼을 누르면
            @Override
            public void onClick(View v) {
                if (isOpen){ // 슬라이딩 레이아웃이 열려져 있으면
                   sliding.startAnimation(slidingRight); // 슬라이딩 레이아웃 닫기
                } else { // 슬라이딩 레이아웃이 닫혀져 있으면
                    sliding.setVisibility(View.VISIBLE); // 슬라이딩 레이아웃을 보이게하기
                   sliding.startAnimation(slidingLeft); // 슬라이딩 레이아웃 열기
                }
            }
        });
    }
 
    class  SlidingAnimationListener implements Animation.AnimationListener {
        @Override
        public void onAnimationStart(Animation animation) {
 
        }
 
        @Override
        public void onAnimationEnd(Animation animation) { // 애니메이션이 끝날 때 자동 호출됨
            if(isOpen) { // 슬라이딩 레이아웃의 열린 상태가 끝나면
                sliding.setVisibility(View.INVISIBLE); // 슬라이딩 레이아웃 안보이게 하고
                btn.setText("열기"); // 버튼에 있는 텍스트를 열기로 하기
                isOpen = false// 닫기 상태가 됨
            } else { // 슬라이딩 레이아웃의 닫힌 상태가 끝나면
                btn.setText("닫기"); // 버튼에 있는 텍스트를 닫기로 하기
                isOpen = true// 열기 상태가됨
            }
        }
 
        @Override
        public void onAnimationRepeat(Animation animation) {
 
        }
    }
}
cs

 

 

이렇게 페이지 슬라이딩(Page Sliding) 기능을 간단하게 알아보았다.

 

슬라이딩 레이아웃 닫혔을 때
슬라이딩 레이아웃 열렸을 때

 

 

 

피드백은 언제나 환영입니다. 

반응형
반응형

(210518 수정)

 

리사이클러뷰(RecyclerView)는 안드로이드 개발을 할 때에 굉장히 많이 사용된다.

왜냐하면 가장 많이 사용되는 UI의 모양들 중 하나이기 때문이다.

이전에 게시했던 채팅앱만 봐도 리사이클러뷰로 구현했다는 것을 알 수 있을 것이다.

 

기본적으로 사용되는 ListView와 비슷하지만 ListView의 기능에 추가적으로

레이아웃 설정도 자유롭고(유연성 증가), 효율성도 더 좋다(성능 발전).

 

RecyclerView가 어떤 것인지 간단하게 알아보고

이에 대한 구현 방법을 알아보겠다.

 

우선 리사이클러뷰(RecyclerView)란 무엇인가?

 

첫 번째,

언뜻 보면 비슷해보이지만

RecyclerView가 ListView와 다른 점은 재활용, 즉 재사용을 할 수 있다는 뷰라는 점에서

차이점을 보인다. 재사용을 하면 어떻게 다를까?

 

이전에 올린 채팅앱을 예시로 설명해보자.

예를 들어

100개의 채팅 메시지가 있고 화면에는 10개의 채팅 메시지만 보인다.

(채팅앱에서의 뷰 객체라고 하면 보통 말풍선을 나타내는 아이템 뷰이다.)

 

여기서 ListView는,

사용자가 스크롤을 위아래로 움직이면 맨 위에 있는 뷰 객체가 삭제되고

맨 아래에 새롭게 뷰 객체가 생성되는 것을 수 십번 수 백번 그 이상의 갱신을 반복하게 된다.

이렇게 되면 결과적으로

성능을 저하시키게 되는 요인이 된다.

 

하지만 RecyclerView는?

맨 위의 뷰 객체를 삭제하지 않고 맨 아래로 이동시켜

재사용(Recycle)을 한다.

하지만 뷰 객체 자체를 재사용하는 것일뿐,

뷰 객체가 가지고 있는 데이터는 새롭게 만들어진다.

 

화면에 10개의 채팅 메시지만 보이면 되기 때문에

몇 백 몇 천개의 채팅 메시지 데이터가 있든 말든

결국 뷰 객체는 10개만 있으면 된다.

 

두 번째,

레이아웃을 보다 유연하게 설정할 수 있다.

수직으로만 가능했던 ListView 한계점을 극복하고

RecyclerView에서는 수평도 가능하다.

 

이 부분은 LayoutManager라는 객체를 사용해서 설정하는데,

아래 코드들에서 다뤄볼 것이다.

 

-----------------------------------------------------------------------------------------

 

이제부터 RecyclerView를 간단하게 구현하면서 알아보자.

 

이번에는 카카오톡 같은 채팅앱의 채팅창 화면이 아닌

채팅할 친구목록을 구현해 볼 것이다.

 

먼저, 메인 친구목록(RecyclerView) 화면을 보여주는 activity_main.xml부터 작성해보자.

 

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
 
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
 
</LinearLayout>
cs

간단하게 RecyclerView만 구현해볼 것이기 때문에 (아직은 껍데기 상태의) RecyclerView만 넣는다.

 

그리고 친구목록을 보여주는 각 친구의 간단프로필을 list_item.xml로 만들건데,

이것은 어댑터의 인플레이션을 통해 뷰 객체가 될 것이다. 

 

list_item.xml

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@drawable/item_line">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
 
        <ImageView
            android:id="@+id/iv"
            android:layout_width="77dp"
            android:layout_height="match_parent" />
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_weight="1">
 
            <TextView
                android:id="@+id/tvName"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="이름"
                android:textSize="40sp" />
 
            <TextView
                android:id="@+id/tvStmsg"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="상태 메시지"
                android:textSize="25sp" />
            
        </LinearLayout>
    </LinearLayout>
</LinearLayout>
cs

 

아이템에 테두리를 만들어주고 코너를 둥글게 한다.

 

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
 
    <stroke android:width="1dp" />
 
    <corners
        android:radius="15dp" />
</shape>
cs

</shape>

친구의 간단프로필을 보여줄 아이템

그 다음으로는 친구목록의 데이터 정보를 담고 있는 Friends.java 라는 모델을 만든다.

Friends.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.example.recyclerviewtest;
 
public class Friends {
    String name; // 이름
    String stateMsg; // 상태메시지
    int resId; // 이미지 아이디
 
    public Friends(String name, String stateMsg, int resId) {
        this.name = name;
        this.stateMsg = stateMsg;
        this.resId = resId;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getStateMsg() {
        return stateMsg;
    }
 
    public void setStateMsg(String stateMsg) {
        this.stateMsg = stateMsg;
    }
 
    public int getResId() {
        return resId;
    }
 
    public void setImageView(int resId) {
        this.resId = resId;
    }
}
 
cs
 
 
 

 

처음에 만들었던 activity_main.xml에서의 RecyclerView는 껍데기에 불과하다.

이 RecyclerView를 실제로 화면에 보여지게 하려면 

어댑터(Adapter)를 통해서 데이터와 연결을 해야한다.

 

FriendsAdapter.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class FriendsAdapter extends RecyclerView.Adapter<FriendsAdapter.ViewHolder> {
 
    ArrayList<Friends> items = new ArrayList<Friends>(); // 아이템 리스트, 여러 아이템을 어댑터가 관리해야하기 때문에 어레이 리스트형 변수 선언
 
    @NonNull
    @Override // 리사이클러뷰에서 보여지는 여러 개의 아이템은 내부에서 캐시되기 때문에 아이템 갯수만큼 객체가 만들어지는 것은 아님
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());// 인플레이션을 하려면 Context 객체가 필요한데 뷰 그룹 객체의 getContext()를 이용하면 Context 참조 가능함함
       View itemView = inflater.inflate(R.layout.list_item, parent, false); // 뷰 그룹 객체는 각 아이템 뷰를 위한 뷰 그룹 객체이므로 xml을 인플레이션하여 이 뷰그룹 객체에 설정
 
        return new ViewHolder(itemView); // 뷰홀더 객체를 생성하면서 뷰 객체를 전달하고 그 뷰홀더 객체를 반환
    }
 
    @Override // 뷰홀더가 재사용될 때 호출되므로 뷰 객체는 기존 것을 그대로 사용하고 데이터만 바꿔줌
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Friends item = items.get(position); // 재활용 가능한 뷰홀더 객체를 파라미터로 전달하기 때문에 그 뷰홀더에 맞는 현재 아이템에 맞는 데이터만 설정(현재 몇 번째 것이 보여야 되는 시점인가)
        holder.setItem(item); // 핸들러에 아이템을 설정
    }
 
    @Override
    public int getItemCount() { // 어댑터가 관리하는 아이템의 갯수를 알아야 할 때 사용
        return items.size();
    }
 
    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;
        TextView tvStmsg;
       ImageView iv;
 
        public ViewHolder(@NonNull View itemView) { // 뷰홀더 생성자로 전달되는 뷰 객체를 참조함(아이템들은 뷰로 만들어지고, 뷰는 뷰홀더에 담아둠)
            super(itemView); // 이 뷰 객체를 부모 클래스의 변수에 담아둠
 
           // 뷰 객체에 들어 있는 텍스트뷰를 참조함
            tvName = itemView.findViewById(R.id.tvName);
            tvStmsg = itemView.findViewById(R.id.tvStmsg);
           iv= itemView.findViewById(R.id.iv);
        }
 
        public void setItem(Friends item) {
            tvName.setText(item.getName());
            tvStmsg.setText(item.getStateMsg());
           iv.setImageResource(item.getResId());
        }
 
    }
 
    // 이 어댑터가 아이템을 위한 Friends 객체를 어레이 리스트 안에 넣어 관리하기 때문에
    // 이 어댑터를 사용하는 소스코드에서 어댑터에 Friends 객체를 넣거나 가져갈 수 있도록 아래의 메소드들을 추가
 
    public void addItem(Friends item) {
        items.add(item); // 어레이 리스트에다 아이템 추가
    }
 
    public void setItems(ArrayList<Friends> items) {
        this.items = items;
    }
 
    public void getItem(int position) {
        items.get(position);
    }
 
    public void setItem(int position, Friends item) {
        items.set(position, item);
    }
}
 
cs}

 

어댑터를 통해서 아이템 뷰와 데이터를 연결했다면

MainActivity 클래스에서

어댑터의 addItem() 메서드를 이용해 Friends 객체를 추가한다.

 

MainActivity.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
 
        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); // 리니어 레이아웃 매니저를 생성해서 레이아웃 방향을 설정함(vertical)
        recyclerView.setLayoutManager(layoutManager); // 리사이클러뷰에 레이아웃 매니저 객체 설정-> 리니어 레이아웃으로 설정됨
        FriendsAdapter adapter = new FriendsAdapter(); // 리사이클러뷰와 어댑터 상호작용
 
        // 어댑터에 아이템을 추가함(Friends 객체의 이름, 상태 메시지, 프로필 이미지)
        adapter.addItem(new Friends("하빌리즘""안녕하세요", R.drawable.flower));
        adapter.addItem(new Friends("비둘기""gugugu", R.drawable.gugu));
        adapter.addItem(new Friends("하빌리즘""안녕하세요", R.drawable.flower));
        adapter.addItem(new Friends("비둘기""gugugu", R.drawable.gugu));
        adapter.addItem(new Friends("하빌리즘""안녕하세요", R.drawable.flower));
        adapter.addItem(new Friends("비둘기""gugugu", R.drawable.gugu));
        adapter.addItem(new Friends("하빌리즘""안녕하세요", R.drawable.flower));
        adapter.addItem(new Friends("비둘기""gugugu", R.drawable.gugu));
        adapter.addItem(new Friends("하빌리즘""안녕하세요", R.drawable.flower));
        adapter.addItem(new Friends("비둘기""gugugu", R.drawable.gugu));
        adapter.addItem(new Friends("하빌리즘""안녕하세요", R.drawable.flower));
        adapter.addItem(new Friends("비둘기""gugugu", R.drawable.gugu));
        adapter.addItem(new Friends("하빌리즘""안녕하세요", R.drawable.flower));
        adapter.addItem(new Friends("비둘기""gugugu", R.drawable.gugu));
        adapter.addItem(new Friends("하빌리즘""안녕하세요", R.drawable.flower));
        adapter.addItem(new Friends("비둘기""gugugu", R.drawable.gugu));
 
        recyclerView.setAdapter(adapter); // 리사이클러뷰에 어댑터를 설정함
 
    }
}
cs}

최종 실행 화면

 

간단하게 RecyclerView에 대해서 알아보았는데

개인적으로 많은 공부가 되었고,

보시는 분들에게 조금이나마 도움이 되셨으면 좋겠다.

 

피드백은 언제나 환영입니다.

반응형
반응형

(210518 수정)

 

이번에는 실제로 채팅을 할 수 있게 채팅 기능을 만들어 보겠다. 

 

앱을 만들 때 상당히 많이 사용하는 RecylerView 를 이용하니 잘 봐두면 좋을 것이다.

 

------------------------------------------------------------------------------------------------------

 

우선 채팅창의 화면은 activity_chat.xml이고 이 xml파일을 Inflation 시켜주는 클래스 파일은

ChatActivity.class 이고 이 클래스 부터 알아보자면

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
public class ChatActivity extends AppCompatActivity {
 
    private static final String TAG = "ChatActivity";
 
    private RecyclerView recyclerView;
    MyAdapter  adapter;
    private RecyclerView.LayoutManager layoutManager; // 리사이클러뷰의 레이아웃을 정해줄 레이아웃 매니저
    FirebaseDatabase database;
    ArrayList<Chat> chatArrayList; // 모델(이메일과 텍스트)을 나열함
 
   EditText edt;
    Button btnSend;
    String stEmail;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);
 
        chatArrayList = new ArrayList<>(); // Chat 모델을 나열하는 ArrayList
 
        database = FirebaseDatabase.getInstance();
 
        stEmail = getIntent().getStringExtra("email"); // 인텐트로 MainActivity에서 입력된 이메일 값을 받아오는 문자열 값 email
 
       edt = (EditText)findViewById(R.id.edt);
 
        recyclerView = (RecyclerView)findViewById(R.id.recyclerview);
        recyclerView.setHasFixedSize(true); // 변경하지 않음 -> 항목의 높이가 바뀌지 않아야 비용이 적게 드므로 성능이 좋음
 
        layoutManager = new LinearLayoutManager(this); // 리사이클러뷰의 레이아웃을 정해줄 레이아웃 매니저
        recyclerView.setLayoutManager(layoutManager); // 리사이클러뷰에 리니어 레이아웃 매니저를 사용함
 
        String[] myDataset = {"테스트1""테스트2","테스트3""테스트4"}; // myDataset에 다음과 같은 문자열 삽입
        adapter = new MyAdapter(chatArrayList, stEmail); // chatArrayList를 어댑터로 연결, 회원의 이메일도 넘김
        recyclerView.setAdapter(adapter); // 리사이클뷰에 어댑터를 설정
 
        btnSend = (Button)findViewById(R.id.btnSend);
        btnSend.setOnClickListener(new View.OnClickListener() { // 메시지 전송 버튼(화살표)을 누르면
            @Override
            public void onClick(View v) {
                String stText = editText.getText().toString(); // 입력창에 입력한 문자가 stText에 담겨짐
                editText.setText(""); // 입력창은 공백이됨
 
                Calendar calendar = Calendar.getInstance(); // 캘린더 객체 인스턴스 calendar
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-dd-MMM HH:mm:ss"); // SimpleDataFormat 이라는 날짜와 시간을 출력하는 객체 생성, hh을 HH로 변경했더니 24시각제로 바뀜
                String datetime = dateFormat.format(calendar.getTime()); // 캘린더 날짜시간 값을 가져와서 문자열인 datatime 으로 변환함
                System.out.println(datetime); // 문자열 datetime 값 출력
 
                DatabaseReference myRef = database.getReference("message").child(datetime); // 데이터베이스에 message 안에 자식으로 날짜인 datatime이 들어감
 
                Hashtable<StringString> numbers // 해시테이블 객체 numbers 생성 후 안에 이메일과 입력한 문자가 들어감
                        = new Hashtable<StringString>();
                numbers.put("email", stEmail);
                numbers.put("text", stText);
 
                myRef.setValue(numbers); // DB 객체에 numbers 객체가 설정됨
            }
        });
 
        ChildEventListener childEventListener = new ChildEventListener() { // 현재 시간의 자식들인 이메일, 텍스트들을 담는 객체 생성
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { // 현재 시간의 자식들을 추가함
                Log.d(TAG, "child를 추가합니다. :" + dataSnapshot.getKey());
 
                // 리스너는 이벤트 발생 시점에 DB에서 지정된 위치에 있던 데이터를 포함하는
                // DataSnapShot을 수신함. getValue()를 호출하면서 데이터의 자바 객체 표현이 반환됨
                Chat chat = dataSnapshot.getValue(Chat.class);
 
                // 모델인 Chat.class 에서 이메일이랑 텍스트를 가져와서 확인
                String stEmail = chat.getEmail();
                String stText = chat.getText();
                Log.d(TAG, "이메일 가져오기 : " + stEmail);
                Log.d(TAG, "텍스트 가져오기 : " + stText);
 
                chatArrayList.add(chat); // Chat 모델을 추가하고
                adapter.notifyDataSetChanged(); // 변경된다는 것을 어댑터에게 알려줌
            }
 
            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
                Log.d(TAG, "Chile가 변경되었습니다. :" + dataSnapshot.getKey());
                String commentKey = dataSnapshot.getKey();
            }
 
            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {
                Log.d(TAG, "CHild가 삭제되었습니다. :" + dataSnapshot.getKey());
                String commentKey = dataSnapshot.getKey();
            }
 
            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
                Log.d(TAG, "Child가 이동되었습니다. :" + dataSnapshot.getKey());
            }
 
            @Override
            public void onCancelled(DatabaseError databaseError) {
                Log.w(TAG, "취소되었습니다. ", databaseError.toException());
            }
        };
 
        DatabaseReference databaseReference = database.getReference("message"); // 데이터베이스에 message 라는 문자로 경로가 만들어짐
        databaseReference.addChildEventListener(childEventListener);
    }
}
cs@Override
 

RecyclerView는 ListView와 비슷하지만

레이아웃을 레이아웃 매니저로 직접 선택할 수 있다는 

차이점이 있다.

 

그리고 데이터를 저장하고 관리하는 Chat Model을 작성한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Chat {
 
    String email;
    String text;
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public String getText() {
        return text;
    }
 
    public void setText(String text) {
        this.text = text;
    }
}
 
cs}

 

Model은 디자인 패턴 중 하나이다.

필자가 디자인 패턴에 대해 아직 많이 미숙하기도 하고, 이 글에서 다룰 내용은 아니지만

Model에 대해서는 간략하게 말할 수 있을 것 같다.

 

Model은 쉽게 말해 어플리케이션의 정보, 즉 데이터를 말한다.

이번 채팅 기능에서의 데이터는 무엇을 말할까?

바로 이메일과 텍스트(채팅 메시지)를 말한다.

 

바로 이러한 Chat Model 데이터로

액티비티와 어댑터, 뷰와 상호작용을 할 수 있게 된다.

 

다음으로는 RecyclerView를 액티비티와 연결하고 설정하는

어댑터에 대해서 알아보자면

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
 
    private ArrayList<Chat> mDataset;
    String stMyEmail = "";
 
    public static class ViewHolder extends RecyclerView.ViewHolder { // 아이템 뷰를 저장하는 뷰홀더
        private final TextView tvChat;
 
        public ViewHolder(View view) {
            super(view);
            tvChat = (TextView) view.findViewById(R.id.tvChat); //  채팅문자가 입력될 텍스트뷰 찾기
        }
 
        public TextView getTextView() { // 채팅문자가 입력된 텍스트뷰 가져오기
            return tvChat;
        }
    }
 
    @Override
    public int getItemViewType(int position) {
        if(mDataset.get(position).email.equals(stMyEmail)) { // email과 stMyEmail과 같으면
            return 1// 내 메시지
        } else { // stMyEail과 다르면
            return 2;  // 내 메시지가 아님
        }
    }
 
    // Chat형 어레이 리스트 myDataset을 처음에 전역변수로 선언한  mDataset에 대입
    public MyAdapter(ArrayList<Chat> myDataset, String stEmail) {
        mDataset = myDataset;
        this.stMyEmail = stEmail;
    }
 
    // 뷰홀더를 생성(레이아웃 생성)
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { // 아이템들을 어떻게 보여줄 것인가
        View view = LayoutInflater.from(viewGroup.getContext()) // 상대방 메시지이면
                .inflate(R.layout.left_text_view, viewGroup, false); // 말풍선이 왼쪽에서 나타나는 xml 파일 인플레이션
 
        if(viewType == 1) { // 내 메시지이면
            view = LayoutInflater.from(viewGroup.getContext())
                    .inflate(R.layout.right_text_view, viewGroup, false); // 말풍선이 오른쪽에서 나타나는 xml 파일 인플레이션
        }
        return new ViewHolder(view);
    }
 
    // 뷰홀더가 재활용될 때 실행
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, final int position) {
        viewHolder.getTextView().setText(mDataset.get(position).getText());
    }
 
    @Override
    public int getItemCount() {
        return mDataset.size();
    }
}
cs

이런식으로 할 수 있다.

 

이렇게 해서

이메일과 패스워드를 입력해서 회원가입을 하고

로그인을 해서 채팅창으로 넘어가면

채팅을 자유롭게 할 수 있다.

 

로그인과 회원가입 화면
채팅창 화면

 

채팅을 하게 되면 파이어 베이스에서 실시간으로 저장된다. 

 

피드백은 언제나 환영입니다.

반응형
반응형

(210518 수정)

 

회원가입을 해서 로그인을 했다면 

이제는 채팅파트를 해보겠다. 

 

우선 채팅 화면 부터 만들고

다음 글에 기능을 다뤄볼 예정이다. 

 

로그인과 회원가입보다는 조금 더 복잡한 코드가 필요하지만

그리 어려운 코드들은 아니니 천천히 참고하면 좋을 듯 하다. 

 

-----------------------------------------------------------------------------------------------------------------------------------

 

MainActivity에서 로그인 버튼을 눌러서 Intent가 발생하게 되면

채팅을 할 수 있는 ChatActivity로 화면 전환을 하게된다.

 

우선 ChatActivity 클래스가 Inflation을 하게 되는 activity_chat.xml 파일을 보게 되면

 

-----------생략--------------- (레이아웃은 ConstraintLayout)

 

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/btnSend"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="@color/purple_200"
tools:ignore="MissingConstraints" />

 

가장 중요한 RecyclerView인데 ListView와 비슷하게 리스트 형식의 뷰를 나타낸다.

(ListView와의 다른 점은 레이아웃을 선택할 수 있다.)

 

background에 본인이 원하는 색상이나 이미지를 넣을 수 있다.

 

<Button
android:id="@+id/btnSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@drawable/ic_baseline_send_24"/>

 

버튼은 우리가 실제로 카카오톡과 같은 채팅 앱을 사용하게 되면

대부분 화살표 모양으로 되어 있는 것을 볼 수 있는데

File - New - Vector asset에서 본인이 원하는 이모티콘 이미지를 가져올 수 있다.

 

<EditText
android:id="@+id/edt"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnSend" />

 

메시지를 직접 입력하는 EditText는 딱히 설명할 건 없지만

전체적인 배치 참고를 위해 넣었다.

 

이 정도로 채팅화면을 위한 xml 구성은 끝

 

그리고 채팅을 하려면 메시지가 입력될 말풍선이 필요함

상대방의 말풍선은 왼쪽에, 자신의 말풍선은 오른쪽에 배치하는데

추가로 left_text_view.xml파일과 right_text_view.xml파일을 작성한다.

 

left_text_view.xml

 

--------------생략--------------(ContraintLayout)

 

<TextView

android:id="@+id/tvChat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@drawable/chat_bubble"
android:padding="10dp"
android:layout_marginStart="5dp"
android:layout_marginBottom="5dp"
tools:ignore="MissingConstraints" />

 

right_text_view.xml 파일은 위 bold부분만

app:layout_constraintEnd_toEndOf="parent" 고쳐주면 된다.

 

또, 말풍선이 나타날 TextView는 작성했지만

아직 말풍선처럼 보이지 않기에

말풍선처럼 꾸며보도록 하겠다.

 

지금까지 레이아웃 xml 파일을 만들었다면

이번에는 드로어블 xml 파일을 만들어보겠다.

 

여기서 drawable파일이라고 하면 화면에 그릴 수 있고 

android:drawable 특성을 사용하여  xml 파일에 적용할 수 있는 그래픽에 대한 것이다.

 

chat_bubble.xml

 

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

<solid
android:color="#90CDD5" />

<stroke
android:width="2dp"
android:color="#000000" />

<corners
android:radius="15dp" />

</shape>

 

말풍선의 모양을 수정할 것이기 때문에 shape로 하고

solid의 color는 색상, 

stroke의 width는 테두리의 굵기, color는 색상,

corners의 radius는 꼭짓점을 둥글게 할 수 있다.

 

 

 

피드백은 언제나 환영입니다. 

 

반응형
반응형

(210518 수정)

우리 일상생활 속

스마트폰으로 가장 많이 사용 되고 있는 카카오톡.

 

다른 앱들보다 친근한 앱이기에

더 재밌게 공부할 수 있었다. 

 

채팅을 하려면 상대방과 채팅을 주고 받으며 대화를 해야 하기 때문에

간단한 회원가입을 하고 로그인을 하는 법 부터 공부해보겠다. 

----------------------------------------------------------------------------------------------

 

첫 화면인 로그인화면을 activity_main.xml(ConstraintLayout)로 설정하고

이 xml파일을 인플레이션 시키는 액티비티를 MainActivity.class로 설정한다.

 

----생략------- (레이아웃은 ConstraintLayout, 로그인 버튼 id=login, 회원가입 버튼 id=register)

 

<EditText
android:id="@+id/etId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

android:hint="ID"

android:ems="10"

android:layout_marginTop="36dp"
android:inputType="textEmailAddress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />

 

hint는 이름 그대로 힌트.

이 EditText가 어떤 내용의 위젯인지 힌트를 주는데, 문자를 하나라도 입력하면 보이지 않는다.

 

ems는 너비.

 

inputType을 textEmailAddress로 주면 @을 사용할 수 있는 문자열 키보드로 바뀌게 된다.

 

<EditText
android:id="@+id/etPw"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"

android:hint="PASSWORD"

android:ems="10"
android:inputType="textPassword"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/etId" />

 

 

inputType을 textPassword로 주면 문자를 입력할 때마다

비밀번호 노출방지를 위해

문자들이 검은색 동그라미들로 바뀐다.



<ProgressBar
android:id="@+id/progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"
tools:ignore="MissingConstraints" />

-visibility 설정-

visible : 보임

invisible : 있는데 안보임

gone : 사라져서 안보임 

 

activity_main.xml의 간단하지만 핵심적인 내용들을 몇 가지 적어봤고

다음으로는 기능을 구현할 MainActivity.class를 보겠다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
public class MainActivity extends AppCompatActivity {
 
    EditText etId;
    EditText etPw;
    private FirebaseAuth mAuth; // 현재 Firebase 인증 상태 확인 변수
    private static final String TAG = "MainActivity";
    ProgressBar progressBar;
    FirebaseDatabase database;
 
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        database = FirebaseDatabase.getInstance(); // FirebaseDatabase 인스턴스를 초기화
        mAuth = FirebaseAuth.getInstance(); // FirebaseAuth 인스턴스를 초기화
 
        etId = (EditText) findViewById(R.id.etId);
        etPw = (EditText) findViewById(R.id.etPw);
        progressBar = (ProgressBar)findViewById(R.id.progressbar);
 
        Button login = (Button)findViewById(R.id.login);
        login.setOnClickListener(new View.OnClickListener() { // 로그인 버튼을 누르면 onClick 메서드 발생
 
            @Override
            public void onClick(View v) {
                String stEmail = etId.getText().toString(); // 아이디 입력창에 입력한 문자열을 stEmail에 대입
                String stPw = etPw.getText().toString(); // 패스워드 입력창에 입력한 문자열을 stPw에 대입
 
                if(stEmail.isEmpty()) { // 아이디 입력창이 비어있으면
                    Toast.makeText(MainActivity.this"이메일을 입력하세요. ", Toast.LENGTH_SHORT).show();
                    return;
                }
                if (stPw.isEmpty()) { // 패스워드 입력창이 비어있으면
                    Toast.makeText(MainActivity.this"패스워드를 입력해주세요. ", Toast.LENGTH_SHORT).show();
                    return;
                }
 
                progressBar.setVisibility(View.VISIBLE); // 로그인 버튼을 누르면 gone에서 visible로 바뀌면서 프로그래스바가 보여지게됨
 
                Button register = (Button)findViewById(R.id.register);
                register.setOnClickListener(new View.OnClickListener() { // 회원가입 버튼을 누르면
                    @Override
                    public void onClick(View v) {
 
                        String stEmail = etId.getText().toString(); // 아이디 입력창에 입력한 문자열을 stEmail에 대입함
                        String stPw = etPw.getText().toString(); // 패스워드 입력창에 입력한 문자열을 stPw에 대입함
 
                        if(stEmail.isEmpty()) { // stEmail이 비어있다면
                            Toast.makeText(MainActivity.this"이메일을 입력하세요. ", Toast.LENGTH_SHORT).show();
                            return;
                        }
 
                        if(stPw.isEmpty()) { // stPW이 비어있다면
                            Toast.makeText(MainActivity.this"패스워드를 입력하세요. ", Toast.LENGTH_SHORT).show();
                            return;
                        }
 
                        progressBar.setVisibility(View.VISIBLE); // 로그인 버튼을 누르면 visible로 바뀌면서 프로그래스바가 보여지게됨
 
                        // 신규 회원가입
                        mAuth.createUserWithEmailAndPassword(stEmail, stPw)
                                .addOnCompleteListener(MainActivity.thisnew OnCompleteListener<AuthResult>() {
                                    @Override
                                    public void onComplete(@NonNull Task<AuthResult> task) {
 
                                        progressBar.setVisibility(View.GONE); // 로딩이 끝나면 gone으로 바뀌면서 사라짐
 
                                        if(task.isSuccessful()) { // 회원가입에 성공했다면
                                            Log.d(TAG,  "이메일 생성에 성공하였습니다. ");
                                            FirebaseUser user = mAuth.getCurrentUser(); // 로그인한 회원의 계정정보 가져오기
 
                                            DatabaseReference myRef = database.getReference("users").child(user.getUid()); // 회원가입을 하면 유니크아이디가 생기는데 user의 chile에 생성
 
                                            Hashtable<StringString> numbers // 해시테이블에 이메일과 텍스트가 들어감
                                                    = new Hashtable<StringString>();
                                            numbers.put("email", user.getEmail()); // 회원의 이메일 가져오기
                                            Toast.makeText(MainActivity.this"회원가입 성공", Toast.LENGTH_SHORT).show(); // 회원가입을 하면 user 데이터베이스에 정보가 들어감
 
                                            myRef.setValue(numbers);
 
                                        } else {
                                            Log.w(TAG, "이메일 생성에 실패하였습니다. ", task.getException());
                                            Toast.makeText(MainActivity.this"이메일 생성에 실패하였습니다. ", Toast.LENGTH_SHORT).show();
                                        }
                                    }
                                });
                    }
                });
 
                // 기존 사용자 로그인
                mAuth.signInWithEmailAndPassword(stEmail, stPw)
                        .addOnCompleteListener(MainActivity.thisnew OnCompleteListener<AuthResult>() {
                            @Override
                            public void onComplete(@NonNull Task<AuthResult> task) {
                                progressBar.setVisibility(View.GONE); // 로딩이 끝나면 gone으로 바뀌면서 프로그래스바가 다시 안보여짐
 
                                if(task.isSuccessful()) { // 로그인에 성공한다면
                                    Log.d(TAG,  "이메일 로그인에 성공하였습니다. ");
 
                                    FirebaseUser user = mAuth.getCurrentUser(); // 로그인한 회원의 계정 정보 가져오기
 
                                    String stUserEmail = user.getEmail(); // 사용자의 이메일을 가져와서 문자열 레퍼런스 stUserEmail에 대입
                                    Log.d(TAG, "회원님의 이메일: " + stUserEmail );
 
                                    Toast.makeText(MainActivity.this"로그인이 되었습니다. ", Toast.LENGTH_SHORT).show();
 
                                    SharedPreferences sharedPref = getSharedPreferences("shared", Context.MODE_PRIVATE); //
                                    SharedPreferences.Editor editor = sharedPref.edit();
                                    editor.putString("email", stUserEmail);
                                    editor.commit(); // editor에 commit()을 사용하여 디크스에 동기적으로 쓸 수 있음
 
                                    Intent intent = new Intent(MainActivity.this, ChatActivity.class);
                                    intent.putExtra("email", stEmail); // 로그인하면서 채팅창으로 화면전환할 때 이메일도 넘기기
                                    startActivity(intent);
 
 
                                } else { // 로그인에 실패하면
                                    Log.w(TAG, "이메일 로그인에 실패하였습니다. ", task.getException());
                                    Toast.makeText(MainActivity.this"로그인에 실패하였습니다. ", Toast.LENGTH_SHORT).show();
                                }
                            }
                        });
            }
        });
    }
 
    @Override
    public void onStart() {
        super.onStart();
        FirebaseUser currentUser = mAuth.getCurrentUser(); // 로그인한 회원의 계정 데이터 정보 가져오기
    }
}
cs
 

메인 화면

 

회원가입을 하면 users 안에 화원정보가 들어간다.

피드백은 언제나 환영입니다. 

반응형
반응형

내가 지금까지 알고 있는 내용이나

공부할 내용들을 다룰 것이고

 

가끔 취미생활인 큐브 관련해서 게시글을 작성할 예정

잘 부탁드림

 

 

 

그나저나 

어제(4.9) 티스토리를 가입해서 블로그를 개설했는데 

실수로 블로그 도메인을 너무 길게해서(habilismprogramming)...

블로그 폐쇄해야 도메인 변경할 수 있대서...

방금 폐쇄하고 다시 만듬...ㅋㅋ

 

 

반응형

'잡담' 카테고리의 다른 글

[211107] 하빌리즘 Habilism -> Habilithm  (0) 2021.11.08

+ Recent posts