티스토리 뷰
GitHubService Ex
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.example.neofuture.recyclerview1"
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.0'
implementation 'com.android.support:cardview-v7:27.1.0'
implementation 'com.android.support:design:27.1.0'
// HTTP로 이미지를 로딩하는 라이브러리
implementation 'com.github.bumptech.glide:glide:3.6.1'
// API 액세스
implementation 'com.squareup.retrofit2:retrofit:2.0.0'
implementation 'com.squareup.retrofit2:converter-gson:2.0.0'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.0.1'
// RxJava
implementation 'io.reactivex:rxjava:1.0.17'
implementation 'io.reactivex:rxandroid:1.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.neofuture.recyclerview1">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".NewGitHubReposApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".DetailActivity"></activity>
</application>
</manifest>
public class NewGitHubReposApplication extends Application {
private Retrofit retrofit;
private GitHubService gitHubService;
@Override
public void onCreate() {
super.onCreate();
setupAPIClient();
}
private void setupAPIClient() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Log.d("API LOG", message);
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(loggingInterceptor).build();
retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
gitHubService = retrofit.create(GitHubService.class);
}
public GitHubService getGitHubService() {
return gitHubService;
}
}
public class MainActivity extends AppCompatActivity implements RepositoryAdapter.OnRepositoryItemClickListener{
private Spinner languageSpinner;
private ProgressBar progressBar;
private CoordinatorLayout coordinatorLayout;
private RepositoryAdapter repositoryAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setView();
}
private void setView() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
RecyclerView recyclerView = findViewById(R.id.recycler_repos);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
repositoryAdapter = new RepositoryAdapter((RepositoryAdapter.OnRepositoryItemClickListener) this, (Context) this );
recyclerView.setAdapter(repositoryAdapter);
progressBar = findViewById(R.id.progress_bar);
// 스낵바
coordinatorLayout = findViewById(R.id.coordinator_layout);
// Spinner
languageSpinner = findViewById(R.id.language_spinner);
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item);
adapter.addAll("java", "objective-c", "swift", "groovy", "python", "ruby", "c");
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
languageSpinner.setAdapter(adapter);
languageSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String language = (String) languageSpinner.getItemAtPosition(position);
loadRepositories(language);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
private void loadRepositories(String language) {
// 로딩중 진행바 표시
progressBar.setVisibility(View.VISIBLE);
// 일주일전 일자 셋팅
final Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -7);
String text = android.text.format.DateFormat.format("yyyy-MM-dd", calendar).toString();
// Retrofit
final NewGitHubReposApplication application = (NewGitHubReposApplication) getApplication();
// language 요청 전달
Observable<GitHubService.Repositories> observable = application.getGitHubService().listRepos("language:" + language + " " + "created:>" + text);
// 입출력 스레드 통신 , 메인스레드에서 결과 수신 처리
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Subscriber<GitHubService.Repositories>() {
@Override
public void onCompleted() {
Log.d("TEST", "ok ~ ");
}
@Override
public void onError(Throwable e) {
Log.d("TEST", e.getMessage());
}
@Override
public void onNext(GitHubService.Repositories repositories) {
progressBar.setVisibility(View.GONE);
repositoryAdapter.setItemsAndRefresh(repositories.items);
}
});
}
@Override
public void onRepositoryItemClick(GitHubService.RepositoryItem item) {
DetailActivity.start(this, item.full_name);
}
}
public interface GitHubService {
@GET("search/repositories?sort=stars&order=desc")
Observable<Repositories> listRepos(@Query("q") String query);
@GET("repos/{repoOwner}/{repoName}")
Observable<RepositoryItem> detailRepo(@Path(value = "repoOwner") String owner, @Path(value = "repoName") String repoName);
public static class Repositories {
public final List<RepositoryItem> items;
public Repositories(List<RepositoryItem> items) {
this.items = items;
}
}
/**
* API 액세스 결과가 이 클래스에 들어온다
* GitHub의 리포지토리 데이터가 들어와 있다
* @see GitHubService#detailRepo(String, String)
*/
public static class RepositoryItem {
public final String description;
public final Owner owner;
public final String language;
public final String name;
public final String stargazers_count;
public final String forks_count;
public final String full_name;
public final String html_url;
public RepositoryItem(String description, Owner owner, String language, String name, String stargazers_count, String forks_count, String full_name, String html_url) {
this.description = description;
this.owner = owner;
this.language = language;
this.name = name;
this.stargazers_count = stargazers_count;
this.forks_count = forks_count;
this.full_name = full_name;
this.html_url = html_url;
}
}
/**
* GitHub의 리포지토리에 대한 오너의 데이터가 들어와 있다
* @see GitHubService#detailRepo(String, String)
*/
public static class Owner {
public final String received_events_url;
public final String organizations_url;
public final String avatar_url;
public final String gravatar_id;
public final String gists_url;
public final String starred_url;
public final String site_admin;
public final String type;
public final String url;
public final String id;
public final String html_url;
public final String following_url;
public final String events_url;
public final String login;
public final String subscriptions_url;
public final String repos_url;
public final String followers_url;
public Owner(String received_events_url, String organizations_url, String avatar_url, String gravatar_id, String gists_url, String starred_url, String site_admin, String type, String url, String id, String html_url, String following_url, String events_url, String login, String subscriptions_url, String repos_url, String followers_url) {
this.received_events_url = received_events_url;
this.organizations_url = organizations_url;
this.avatar_url = avatar_url;
this.gravatar_id = gravatar_id;
this.gists_url = gists_url;
this.starred_url = starred_url;
this.site_admin = site_admin;
this.type = type;
this.url = url;
this.id = id;
this.html_url = html_url;
this.following_url = following_url;
this.events_url = events_url;
this.login = login;
this.subscriptions_url = subscriptions_url;
this.repos_url = repos_url;
this.followers_url = followers_url;
}
}
}
class RepositoryAdapter extends RecyclerView.Adapter<RepositoryAdapter.RepoViewHolder> {
private final OnRepositoryItemClickListener onRepositoryItemClickListener;
private final Context context;
private List<GitHubService.RepositoryItem> items;
public RepositoryAdapter(OnRepositoryItemClickListener onRepositoryItemClickListener, Context context) {
this.onRepositoryItemClickListener = onRepositoryItemClickListener;
this.context = context;
}
public void setItemsAndRefresh(List<GitHubService.RepositoryItem> items) {
this.items = items;
notifyDataSetChanged();
}
/**
* RecyclerView의 아이템 뷰 생성과 뷰를 유지할 ViewHolder를 생성 한다
*/
@Override
public RepoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(context).inflate(R.layout.repo_item, parent, false);
return new RepoViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull final RepoViewHolder holder, int position) {
final GitHubService.RepositoryItem item = getItemAt(position);
// 뷰 클릭 후 클릭된 아이템을 listener 에게 알린다
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onRepositoryItemClickListener.onRepositoryItemClick(item);
}
});
holder.repoName.setText(item.name);
holder.repoDetail.setText(item.description);
holder.starCount.setText(item.stargazers_count);
Glide.with(context)
.load(item.owner.avatar_url)
.asBitmap().centerCrop().into(new BitmapImageViewTarget(holder.repoImage) {
@Override
protected void setResource(Bitmap resource) {
// image round
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.create(context.getResources(), resource);
circularBitmapDrawable.setCircular(true);
holder.repoImage.setImageDrawable(circularBitmapDrawable);
}
});
}
public GitHubService.RepositoryItem getItemAt(int position) {
return items.get(position);
}
@Override
public int getItemCount() {
if (items == null) {
return 0;
}
return items.size();
}
public interface OnRepositoryItemClickListener {
void onRepositoryItemClick(GitHubService.RepositoryItem item);
}
static class RepoViewHolder extends RecyclerView.ViewHolder {
private final TextView repoName;
private final TextView repoDetail;
private final ImageView repoImage;
private final TextView starCount;
public RepoViewHolder(View itemView) {
super(itemView);
repoName = (TextView) itemView.findViewById(R.id.repo_name);
repoDetail = (TextView) itemView.findViewById(R.id.repo_detail);
repoImage = (ImageView) itemView.findViewById(R.id.repo_image);
starCount = (TextView) itemView.findViewById(R.id.repo_star);
}
}
}
public class DetailActivity extends AppCompatActivity {
private static final String EXTRA_FULL_REPOSITORY_NAME = "EXTRA_FULL_REPOSITORY_NAME";
private TextView fullNameTextView;
private TextView detailTextView;
private TextView repoStarTextView;
private TextView repoForkTextView;
private ImageView ownerImage;
public static void start(Context context, String fullRepositoryName) {
final Intent intent = new Intent(context, DetailActivity.class);
intent.putExtra(EXTRA_FULL_REPOSITORY_NAME, fullRepositoryName);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
fullNameTextView = (TextView) DetailActivity.this.findViewById(R.id.fullname);
detailTextView = (TextView) findViewById(R.id.detail);
repoStarTextView = (TextView) findViewById(R.id.repo_star);
repoForkTextView = (TextView) findViewById(R.id.repo_fork);
ownerImage = (ImageView) findViewById(R.id.owner_image);
final Intent intent = getIntent();
final String fullRepoName = intent.getStringExtra(EXTRA_FULL_REPOSITORY_NAME);
loadRepositories(fullRepoName);
}
private void loadRepositories(String fullRepoName) {
final String[] repoData = fullRepoName.split("/");
final String owner = repoData[0];
final String repoName = repoData[1];
final GitHubService gitHubService = ((NewGitHubReposApplication) getApplication()).getGitHubService();
gitHubService.detailRepo(owner, repoName)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<GitHubService.RepositoryItem>() {
@Override
public void call(GitHubService.RepositoryItem response) {
setupRepositoryInfo(response);
}
});
}
private void setupRepositoryInfo(final GitHubService.RepositoryItem response) {
fullNameTextView.setText(response.full_name);
fullNameTextView.setText(response.full_name);
detailTextView.setText(response.description);
repoStarTextView.setText(response.stargazers_count);
repoForkTextView.setText(response.forks_count);
Glide.with(DetailActivity.this)
.load(response.owner.avatar_url)
.asBitmap().centerCrop().into(new BitmapImageViewTarget(ownerImage) {
@Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable roundedBitmapDrawable =
RoundedBitmapDrawableFactory.create(getResources(), resource);
roundedBitmapDrawable.setCircular(true);
ownerImage.setImageDrawable(roundedBitmapDrawable);
}
});
// 로고 리포지토리 이름을 탭 작자 페이지 리스너 ~~
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(response.html_url)));
} catch (Exception e) {
Snackbar.make(findViewById(android.R.id.content), "링크 열기 불가 ", Snackbar.LENGTH_LONG);
}
}
};
fullNameTextView.setOnClickListener(listener);
ownerImage.setOnClickListener(listener);
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator_layout"
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:fitsSystemWindows="true"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</android.support.design.widget.CollapsingToolbarLayout>
<Spinner
android:id="@+id/language_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:fitsSystemWindows="true"
/>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/activity_repository_list"/>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</android.support.design.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="@layout/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_repos"
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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/repo_item">
</android.support.v7.widget.RecyclerView>
</merge>
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView 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="wrap_content"
android:layout_margin="8dp"
android:clickable="true"
android:foreground="?selectableItemBackground"
android:orientation="vertical"
app:cardUseCompatPadding="true"
tools:showIn="@layout/activity_repository_list">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<ImageView
android:id="@+id/repo_image"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="8dp" />
<TextView
android:id="@+id/repo_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@+id/repo_star_image"
android:layout_toRightOf="@id/repo_image"
android:textAppearance="@android:style/TextAppearance.Large"
tools:text="title" />
<TextView
android:id="@+id/repo_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/repo_name"
android:layout_marginBottom="4dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="4dp"
android:layout_toRightOf="@id/repo_image"
android:maxLines="20"
android:textAppearance="@android:style/TextAppearance"
tools:text="contents" />
<TextView
android:id="@+id/repo_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_margin="8dp"
android:textAppearance="@android:style/TextAppearance"
tools:text="contents" />
<ImageView
android:id="@id/repo_star_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@id/repo_star"
android:src="@drawable/ic_star_black_18dp" />
</RelativeLayout>
</android.support.v7.widget.CardView>
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".DetailActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp">
<ImageView
android:id="@+id/owner_image"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_centerHorizontal="true"
android:layout_margin="8dp"
tools:src="@android:drawable/sym_def_app_icon"/>
<TextView
android:id="@+id/fullname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@id/owner_image"
android:layout_marginBottom="32dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@+id/repo_star_image"
android:textAppearance="@android:style/TextAppearance.Large"
tools:text="owner/reponame"/>
<TextView
android:id="@+id/detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/fullname"
android:layout_margin="8dp"
android:textAppearance="@android:style/TextAppearance"
tools:text="this is detail text. aaaaaaaaaaaaaaaaaaaaaaaaa"/>
<TextView
android:id="@+id/repo_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@id/owner_image"
android:layout_margin="8dp"
android:textAppearance="@android:style/TextAppearance"
tools:text="contents"/>
<ImageView
android:id="@id/repo_star_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/owner_image"
android:layout_marginBottom="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@id/repo_star"
android:src="@drawable/ic_star_black_18dp"/>
<TextView
android:id="@+id/repo_fork"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@id/repo_star"
android:layout_margin="8dp"
android:textAppearance="@android:style/TextAppearance"
tools:text="contents"/>
<ImageView
android:id="@+id/repo_fork_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/repo_star"
android:layout_marginBottom="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@id/repo_star"
android:src="@drawable/ic_call_split_black_18dp"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:spinnerItemStyle">@style/SpinnerItem</item>
<item name="android:spinnerDropDownItemStyle">@style/SpinnerItem.DropDownItem</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="SpinnerItem" parent="@android:style/Widget.TextView.SpinnerItem">
<item name="android:textColor">#ffffff</item>
</style>
<style name="SpinnerItem.DropDownItem" parent="@android:style/Widget.DropDownItem.Spinner">
<item name="android:textColor">#ffffff</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
</resources>
'android' 카테고리의 다른 글
14. BIM 계산 (Service, LocalBroadcastManager 이용방법) (0) | 2018.08.03 |
---|---|
13 mvvm GitHubService (0) | 2018.07.11 |
11.ContentProbider을 알자 (0) | 2018.05.26 |
10.IntentService를 활용하자 (0) | 2018.05.26 |
9. 상주 서비스를 만들자 (0) | 2018.05.23 |