본문 바로가기
안드로이드 자바

[Java][Android] 내 스마트폰 mp3 파일 플레이어 빠르게 구현하기

by teamnova 2021. 2. 18.

안녕하세요.

안드로이드 스튜디오에서 자바로 음악 플레이어를 빠르게 구현하는 방법을

공유하겠습니다.

 

내 스마트폰에 저장되어 있는 mp3 파일을 읽어와서 음악을 재생하는 앱입니다.

 


사전 준비 사항

 

1. 스틱코드 회원가입

(https://stickode.com/signup.html)

 

2. 안드로이드 스튜디오에 스틱 코드 설치 및 로그인

(https://stickode.com/howto.html#jetbrains_installation)

 

3. 스틱코드 접속하여 음악 플레이어 구현하기 포스팅 즐겨찾기 추가


스틱 코드 음악 플레이어 포스팅 URL

https://stickode.com/detail.html?no=1902

 

스틱코드

 

stickode.com

 

1. 스틱코드로 manifest 퍼미션 추가

즐겨 찾기한 포스트 코드 불러오기

안드로이드 스튜디오에 스틱 코드 로그인이 되어 있다면,

mani 만 쳐도 포스트에 등록되어 있는 코드 목록이 보이는 것을 확인할 수 있습니다.

 

manifest 퍼미션 추가 코드를 불러오시면 됩니다.

 

2. main activity 추가

main activity java 코드 불러오기

 

main activity 클릭하면

main activity 코드가 불러와 집니다.

 

 

3. Music Player 추가

  • 새로운 activity 추가하는 방법 (java파일이 있는 폴더 오른쪽 마우스 클릭)

새로운 activity 추가 방법 1

New -> Activity -> Empty Activity

 

새로운 activity 추가 방법 2

Activity Name 부분에 MusicPlayerActivity 입력 후 Finish 클릭

 

music player activity java 코드 불러오기

새로 만들어진 MusicPlayer.java 파일에서 music 단어를 치면

music player activity 목록이 나옵니다.

 

해당 목록을 클릭하면, 자바 코드가 불러와집니다.

 

4. xml 파일 코드 추가

xml 파일 목록

 

xml 파일 코드 불러오기 명칭 (mxl 파일명 => 스틱 코드 포스팅 소스코드 명)

  • activity_main.xml => main_xml
  • activity_music_player.xml => music player_xml
  • song_layout.xml => song list_xml

 

5. 앱에서 사용되는 이미지 찾아서 저장하기

앱에서 사용하는 이미지 목록

앞 곡 재생, 다음 곡 재생, 한곡 반복 재생, 전체곡 반복재생 이미지가 필요합니다.

 

무료 이미지 다운로드 사이트를 찾으셔서 원하는 이미지를 구하셔서 (구글링 하면 금방 나옵니다.)

drawable 폴더 아래에 이미지 이름만 똑같이 변경해서 넣으시면 됩니다.

 

테스트할 디바이스를 연결해서

테스트해보시면 됩니다.

 

 

6. 전체 소스 코드

 

  • main java
package com.project.musicplayer;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    Button music_player_btn;
    private final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 10; //외부 파일 읽기 권한 요청 request 값

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

        music_player_btn = findViewById(R.id.musicBtn);

        //음악 플레이어 버튼 클릭 리스너 셋팅
        //기능: 뮤직플레이어 액티비티로 이동
        music_player_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MusicPlayerActivity.class);
                startActivity(intent);
            }
        });

        //mp3 파일을 스마트폰에서 읽어오기 위해 권한 요청하기
        //외부저장소 읽기/쓰기, 사진, 카메라 권한 부여 확인 및 권한요청 코드
        if (ContextCompat.checkSelfPermission(MainActivity.this,
                Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {

            Log.d("permission","checkSelfPermission");
            // 설명이 필요한가?
            if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                    Manifest.permission.READ_EXTERNAL_STORAGE)) {

                Log.d("permission","shouldShowRequestPermissionRationale");
                // 사용자에게 설명을 보여줍니다.
                // 권한 요청을 다시 시도합니다.

            } else {
                // 권한요청

                Log.d("permission","권한 요청");
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                Manifest.permission.READ_EXTERNAL_STORAGE,
                                Manifest.permission.CAMERA},
                        MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);

            }
        }



    }

    //권한요청을 사용자에게 허락받았는지 못받았는지 결과를 알수 있는 콜백 메서드
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: {

                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                    // 권한 획득 성공
                    Log.d("permission","권한 획득 성공");

                } else {

                    // 권한 획득 실패
                    Log.d("permission","권한 획득 실패");
                }
                return;
            }

        }
    }


}

 

  • main xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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"
    android:orientation="vertical"
    android:gravity="center_vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/musicBtn"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_marginLeft="30dp"
        android:layout_marginTop="10dp"
        android:layout_marginRight="30dp"
        android:background="#323151"
        android:text="음악 플레이어"
        android:textColor="#FFFFFF" />

</LinearLayout>

 

  • music player java
package com.project.musicplayer;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.database.Cursor;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.webkit.MimeTypeMap;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.TextView;

import java.util.ArrayList;

public class MusicPlayerActivity extends AppCompatActivity implements View.OnClickListener  {

    public String log_name = "musicplayer";
    public ListView listView;
    private String songNames[];
    private ArrayList<Uri> songs;
    private String title = "";

    static private TextView titleTv;
    static public Button btnPlay;
    private Button btnBack, btnFor;
    private ImageView forwardIv, backwardIv, repeatIv;

    static public SeekBar seekBar;
    static public MediaPlayer mediaPlayer;
    static private int pos = -1;       //노래 포지션

    static public boolean bearPhoneOneCheck = false;    //bearPhoneOneCheck 이어폰이 한번 뽑히면 true, 그렇지 않으면 false
    static public int CheckearPhone = 0;   //이어폰을 빼면 0, 연결되어 있으면 1이상 값
    static public boolean bpause = false;    //일시 정지 버튼을 눌렀다면, true
    static public int repeatPlayMode = 0;  //반복재생 모드 0: 전곡 반복 재생 / 1: 한곡 반복 재생

    private Runnable runnable;

    private Handler handler;

    private Uri uri;

    @Override
    protected void onResume() {
        super.onResume();
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

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

        titleTv = findViewById(R.id.titleTv);
        listView = findViewById(R.id.listView);

        btnPlay = findViewById(R.id.btnPlay);
        btnBack = findViewById(R.id.btnBack);
        btnFor = findViewById(R.id.btnFor);
        seekBar = findViewById(R.id.seekbar);
        forwardIv = findViewById(R.id.forwardIv);
        backwardIv = findViewById(R.id.backwardIv);
        repeatIv = findViewById(R.id.repeatIv);

        handler = new Handler();

        btnFor.setOnClickListener(this);
        btnBack.setOnClickListener(this);
        btnPlay.setOnClickListener(this);
        forwardIv.setOnClickListener(this);
        backwardIv.setOnClickListener(this);
        repeatIv.setOnClickListener(this);

        //음악이 재생중이였다면 seekBar, 노래제목 표시하기
        if(mediaPlayer != null)
        {
            if(mediaPlayer.isPlaying())
            {
                if(!title.equals("")){
                    titleTv.setSelected(true);
                    titleTv.setText(title);
                }

                seekBar.setMax(mediaPlayer.getDuration());
                seekBar.setProgress(mediaPlayer.getCurrentPosition());
                btnPlay.setText("||");
            }
            else if(!mediaPlayer.isPlaying())
            {
                if(!title.equals("")){
                    titleTv.setSelected(true);
                    titleTv.setText(title);
                }

                seekBar.setMax(mediaPlayer.getDuration());
                seekBar.setProgress(mediaPlayer.getCurrentPosition());
                btnPlay.setText(">");
            }

            if(repeatPlayMode == 0)
            {
                repeatIv.setImageResource(R.drawable.repeat_32px);
            }
            else if(repeatPlayMode == 1)
            {
                repeatIv.setImageResource(R.drawable.repeat_one_32px);
            }
        }

        // MP3 경로를 가질 문자열 배열.
        String[] resultPath = null;

        String selectionMimeType = MediaStore.Files.FileColumns.MIME_TYPE + "=?";
        // 찾고자하는 파일 확장자명.
        String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("mp3");

        String[] selectionArgsMp3 = new String[]{ mimeType };

        Cursor c = null;

        c = getContentResolver().query(
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                new String[]{MediaStore.Audio.Media._ID,MediaStore.Audio.Media.DISPLAY_NAME}, selectionMimeType, selectionArgsMp3, null);


        if (c.getCount() != 0){

            ArrayList<Uri> arrayList = new ArrayList<Uri>();
            songNames = new String[c.getCount()];

            //커서로 조회한 mp3 상대경로 와 mp3 제목을 저장한다.
            while (c.moveToNext()) {

                //음악파일이 저장되어 있는 상대 경로를 uri로 저장.
                Uri contentUri = Uri.withAppendedPath(
                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                        c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media._ID))
                );

                //음악이 저장되어 있는 상대 경로 저장
                arrayList.add(contentUri);

                //음악 제목 저장
                songNames[c.getPosition()] = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)).replace(".mp3", "");

                Log.d(log_name, contentUri.toString());
                Log.d(log_name, songNames[c.getPosition()]);
            }

            Log.d(log_name, String.valueOf(c.getCount()));
            Log.d(log_name, String.valueOf(arrayList.size()));
            songs = arrayList;

        }

        //음악 리스트를 위한 어댑터 구현
        //listview => song_layout textView 위젯에 음악 개수 만큼 음악 제목을 표시한다.
        ArrayAdapter<String> adaper = new ArrayAdapter<String>(getApplicationContext(), R.layout.song_layout
                , R.id.textView, songNames);

        listView.setAdapter(adaper);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                pos = position;

                if (mediaPlayer != null) {
                    mediaPlayer.stop();
                    mediaPlayer.release();
                }

                titleTv.setSelected(true);
                titleTv.setText(songNames[pos]);

                title = (String)titleTv.getText();

                mediaPlayer = MediaPlayer.create(MusicPlayerActivity.this, songs.get(pos));

                mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                    @Override
                    public void onPrepared(MediaPlayer mp) {
                        //곡을 클릭하면 곡의 전체시간을 seekBar에 Max값으로 넣는다.
                        seekBar.setMax(mp.getDuration());
                        mediaPlayer.start();
                        bearPhoneOneCheck = true;
                        changeSeekbar();
                    }
                });

            }
        });




        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser) {
                    //seekBar를 누르면 눌린 시간으로 mp3 시간을 이동
                    mediaPlayer.seekTo(progress);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

    }

    private void changeSeekbar(){

        //현재 mp3 재싱시간을 seekBar에 넣는다.
        seekBar.setProgress(mediaPlayer.getCurrentPosition());

        if(mediaPlayer.isPlaying())
        {

            AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
            AudioDeviceInfo[] audioDevices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
            for (AudioDeviceInfo deviceInfo : audioDevices) {
                if (deviceInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES
                        || deviceInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET) {

                    //이어폰이 연결되어 있다면 +1씩 증가
                    CheckearPhone += 1;
                }
            }

            //이어폰을 빼면 음악 일시정지
            if(CheckearPhone == 0){

                //bearPhoneOneCheck 이어폰이 한번 뽑히면 true, 그렇지 않으면 false
                if(!bearPhoneOneCheck)
                {
                    mediaPlayer.pause();
                    btnPlay.setText(">");
                    bearPhoneOneCheck = true;
                }
                else
                {
                    Log.d(log_name,"이어폰 분리~~~~~~~~~~~~");

                    btnPlay.setText("||");
                    runnable = new Runnable() {
                        @Override
                        public void run() {
                            changeSeekbar();
                        }
                    };

                    handler.postDelayed(runnable, 400);
                }

            }
            else
            {
                CheckearPhone = 0;

                Log.d(log_name,"이어폰 착용!!!!!!!!!!!!");

                btnPlay.setText("||");
                runnable = new Runnable() {
                    @Override
                    public void run() {

                        //이어폰을 다시 연결하면 bearPhoneOneCheck = false 로 변경
                        //bearPhoneOneCheck 는 이어폰을 연결하여 갑자기 뺏을경우 노래가 멈추게 하기위해 만듬
                        bearPhoneOneCheck = false;
                        changeSeekbar();
                    }
                };

                handler.postDelayed(runnable, 100);
            }

        }
        else
        {


            //한곡이 끝나면 자동 재생하려고 했으나, seekBar와 mediaPlayer 시간 값이 정확하게 일지하지 않아
            // 200 ms에 오차 범위를 넣었다.
            Log.d(log_name,"seekbar 끝남 다음곡 재생 : "+ seekBar.getProgress() + " / " + mediaPlayer.getDuration());
            if (!bpause && seekBar.getProgress() >= (mediaPlayer.getDuration() - 1500)) {
                Log.d(log_name, "seekbar 끝남 다음곡 재생 : " + seekBar.getProgress() + " / " + mediaPlayer.getDuration());

                btnPlay.setText(">");

                if (repeatPlayMode == 0) //전곡 반복 재생
                {
                    if (pos != -1 && songNames.length - 1 > pos) {
                        pos += 1;
                        playMusic(pos);

                    } else if (songNames.length - 1 == pos) {
                        pos = 0;
                        playMusic(pos);

                    }
                } else if (repeatPlayMode == 1)    //한곡 반복 재생
                {
                    playMusic(pos);
                }
            }

        }
    }

    @Override
    public void onClick(View v) {
        if(mediaPlayer != null) {

            switch (v.getId()) {
                case R.id.btnPlay:
                    if (mediaPlayer.isPlaying()) {
                        mediaPlayer.pause();
                        bpause = true;
                        btnPlay.setText(">");
                    } else {
                        mediaPlayer.start();
                        btnPlay.setText("||");
                        bpause = false;
                        bearPhoneOneCheck = true;
                        changeSeekbar();
                    }
                    break;

                case R.id.btnFor:
                    mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() + 5000);
                    break;

                case R.id.btnBack:
                    mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() - 5000);
                    break;

                case R.id.forwardIv:
                    if(pos != -1 && songNames.length - 1 > pos)
                    {
                        pos += 1;
                        playMusic(pos);
                    }
                    else if(songNames.length - 1 == pos)
                    {
                        pos = 0;
                        playMusic(pos);
                    }
                    break;

                case R.id.backwardIv:
                    if(pos > 0)
                    {
                        pos -= 1;
                        playMusic(pos);
                    }
                    else if(pos == 0)
                    {
                        pos = songNames.length - 1;
                        playMusic(pos);
                    }
                    break;

                case R.id.repeatIv:
                    if(repeatPlayMode == 0)
                    {
                        repeatPlayMode = 1;
                        repeatIv.setImageResource(R.drawable.repeat_one_32px);
                    }
                    else if(repeatPlayMode == 1)
                    {
                        repeatPlayMode = 0;
                        repeatIv.setImageResource(R.drawable.repeat_32px);
                    }
                    break;
            }
        }
    }

    private void playMusic(int position)
    {

        Log.d(log_name,"playMusic position: " + position);

        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
        }

        Log.d(log_name,"playMusic uri: " + songs.get(position).toString());

        titleTv.setSelected(true);
        titleTv.setText(songNames[position]);

        title = (String)titleTv.getText();

        mediaPlayer = MediaPlayer.create(MusicPlayerActivity.this, songs.get(position));

        mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                //곡을 클릭하면 곡의 전체시간을 seekBar에 Max값으로 넣는다.
                seekBar.setMax(mp.getDuration());
                mediaPlayer.start();
                changeSeekbar();

            }
        });
    }

}

 

  • music player 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=".MusicPlayerActivity">

    <TextView
        android:id="@+id/titleTv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10sp"
        android:layout_marginRight="10sp"
        android:gravity="center_vertical"
        android:textSize="20sp"
        android:text="제목"
        android:textColor="#000000"
        android:singleLine="true"
        android:marqueeRepeatLimit="marquee_forever"
        android:ellipsize="marquee"
        android:scrollHorizontally="true"
        app:layout_constraintBottom_toTopOf="@+id/seekbar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnBack"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="-5"
        android:textSize="20dp"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toStartOf="@+id/btnPlay"
        app:layout_constraintStart_toEndOf="@+id/backwardIv"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />

    <ImageView
        android:id="@+id/backwardIv"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:scaleType="center"
        android:src="@drawable/backward_32px"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toStartOf="@+id/btnBack"
        app:layout_constraintHorizontal_bias="0.4"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />

    <Button
        android:id="@+id/btnPlay"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text=">"
        android:textSize="20dp"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toStartOf="@+id/btnFor"
        app:layout_constraintStart_toEndOf="@+id/btnBack"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />

    <ImageView
        android:id="@+id/forwardIv"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:scaleType="center"
        android:src="@drawable/forward_32px"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/btnFor"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />

    <Button
        android:id="@+id/btnFor"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="+5"
        android:textSize="20dp"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toStartOf="@+id/forwardIv"
        app:layout_constraintStart_toEndOf="@+id/btnPlay"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />

    <ImageView
        android:id="@+id/repeatIv"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:scaleType="center"
        android:src="@drawable/repeat_32px"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/forwardIv"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />



    <androidx.appcompat.widget.AppCompatSeekBar
        android:id="@+id/seekbar"
        android:layout_width="match_parent"
        android:layout_height="40dp"

        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.169" />

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="350sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

  • song list xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:id="@+id/textView"
        android:textSize="20sp"
        android:layout_marginLeft="10sp"
        android:layout_marginRight="10sp"
        android:textColor="#000000"
        android:gravity="center_vertical"
        android:marqueeRepeatLimit="marquee_forever"
        android:ellipsize="marquee"
        android:scrollHorizontally="true"/>


</LinearLayout>