Часто последнее время приходилось делать экраны с бесконечной пагинацией, собралось у меня много наработок по этому поводу, решил поделиться и заодно оставить небольшую заметку для себя ну и для тех кто на нее забредет, что бы по исходникам не рыться каждый раз когда приспичит.
В общем начнем с того я писал про работу с ретрофит вот тут . В этой статье я буду использовать его так же для работы с API. API же я буду использовать от github как вы уже наверно заметили со скриншота и будем мы искать репозитории. Вообще приложение для примера будет очень простое, думаю для человека который раньше уже работал с RecyclerView, Callback'ами и Retrofit все окажется очень даже просто. Ну в общем начнем педалить.
Как всегда начинаем с того что создаем пустой проект в студии, что бы нам не насоздавало кучу разного лишнего кода который нам по сути просто не нужен. Дальше открываем файл app/build.gradle и добавляем нужные нам библиотеки и зависимости, потому что тут как я ранее говорил у нас будет использоваться RecyclerView, Retrofit, а еще ButterKnife для быстрого доступа к вьюхам. Я не буду крамсать файл на куски а сразу покажу вам как будет выглядеть наш файл в итоге.
app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'android-apt'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.project.endlesslist"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile ('proguard-android.txt' ) , 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree (dir: 'libs' , include: ['*.jar' ])
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
compile 'com.jakewharton:butterknife:8.0.1'
apt 'com.jakewharton:butterknife-compiler:8.0.1'
compile 'com.squareup.retrofit2:retrofit:2.0.1'
compile 'com.squareup.retrofit2:converter-gson:2.0.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
compile 'com.squareup.retrofit2:converter-scalars:2.0.1'
}
А еще нам нужно добавить во второй build.gradle одну зависимость для ButterKnife'a, потому что без него не студия не сможет подтянуть apt зависимости.
build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
allprojects {
repositories {
jcenter()
}
}
task clean (type: Delete) {
delete rootProject.buildDir
}
Тут все стандартно, только добавили одну строчку в dependencies.
Дальше давайте добавим в AndroidManifest пермишн для доступа в интернет. У меня он выглядит вот так
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android ="http://schemas.android.com/apk/res/android"
package ="com.project.endlesslist" >
<uses-permission android:name ="android.permission.INTERNET" />
<application
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 >
</application >
</manifest >
Ну и все, теперь мы готовы педалить. Теперь нам нужно создать запрос на сервер. Ссылка на запрос у нас будет выглядеть вот так , но в нашем интерфейсе мы опишем его так что бы нам можно было его динамически изменять, ну собственно как всегда. :)
Создаем наш интерфейс с ссылкой и параметрами. Файл интерфейса назовем API, внутри у нас будет вот такое:
API.java
import com.project.endlesslist.api.model.GithubModel;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface API {
@GET ("search/repositories" )
Call<GithubModel> getSearchedRepos (@Query("q" ) String q,
@Query ("page" ) int page,
@Query ("per_page" ) int perPage) ;
}
Дальше нам нужно создать класс GithubModel и все вложенные в него классы. Этот класс мы создали с помощью сайта www.jsonschema2pojo.org , он специально создан для таких целей, копируте туда json, Source type ставим JSON и Annotation style выбираем Gson. Далее нажимаем Preview внизу экрана и копируем все к себе в аппу или zip и вам скачается все на жесткий диск, ну и дальше перемещаете файлы в свой проект.
В нашем случае я создал пакедж api, в нем у нас лежит уже интерфейс API.java и в нем же я создал пакедж model в который засунул наши сгенерированые файлы моделей.
GithubModel.java
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
import java.util.List;
public class GithubModel {
@SerializedName ("total_count" )
@Expose
private Integer totalCount;
@SerializedName ("incomplete_results" )
@Expose
private Boolean incompleteResults;
@SerializedName ("items" )
@Expose
private List<GithubItemModel> items = new ArrayList<>();
public Integer getTotalCount () {
return totalCount;
}
public void setTotalCount (Integer totalCount) {
this .totalCount = totalCount;
}
public Boolean getIncompleteResults () {
return incompleteResults;
}
public void setIncompleteResults (Boolean incompleteResults) {
this .incompleteResults = incompleteResults;
}
public List<GithubItemModel> getItems () {
return items;
}
public void setItems (List<GithubItemModel> items) {
this .items = items;
}
}
GithubItemModel.java
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class GithubItemModel {
@SerializedName ("id" )
@Expose
private Integer id;
@SerializedName ("name" )
@Expose
private String name;
@SerializedName ("full_name" )
@Expose
private String fullName;
@SerializedName ("owner" )
@Expose
private GithubOwnerModel owner;
@SerializedName ("private" )
@Expose
private Boolean _private;
@SerializedName ("html_url" )
@Expose
private String htmlUrl;
@SerializedName ("description" )
@Expose
private String description;
@SerializedName ("fork" )
@Expose
private Boolean fork;
@SerializedName ("url" )
@Expose
private String url;
@SerializedName ("forks_url" )
@Expose
private String forksUrl;
@SerializedName ("keys_url" )
@Expose
private String keysUrl;
@SerializedName ("collaborators_url" )
@Expose
private String collaboratorsUrl;
@SerializedName ("teams_url" )
@Expose
private String teamsUrl;
@SerializedName ("hooks_url" )
@Expose
private String hooksUrl;
@SerializedName ("issue_events_url" )
@Expose
private String issueEventsUrl;
@SerializedName ("events_url" )
@Expose
private String eventsUrl;
@SerializedName ("assignees_url" )
@Expose
private String assigneesUrl;
@SerializedName ("branches_url" )
@Expose
private String branchesUrl;
@SerializedName ("tags_url" )
@Expose
private String tagsUrl;
@SerializedName ("blobs_url" )
@Expose
private String blobsUrl;
@SerializedName ("git_tags_url" )
@Expose
private String gitTagsUrl;
@SerializedName ("git_refs_url" )
@Expose
private String gitRefsUrl;
@SerializedName ("trees_url" )
@Expose
private String treesUrl;
@SerializedName ("statuses_url" )
@Expose
private String statusesUrl;
@SerializedName ("languages_url" )
@Expose
private String languagesUrl;
@SerializedName ("stargazers_url" )
@Expose
private String stargazersUrl;
@SerializedName ("contributors_url" )
@Expose
private String contributorsUrl;
@SerializedName ("subscribers_url" )
@Expose
private String subscribersUrl;
@SerializedName ("subscription_url" )
@Expose
private String subscriptionUrl;
@SerializedName ("commits_url" )
@Expose
private String commitsUrl;
@SerializedName ("git_commits_url" )
@Expose
private String gitCommitsUrl;
@SerializedName ("comments_url" )
@Expose
private String commentsUrl;
@SerializedName ("issue_comment_url" )
@Expose
private String issueCommentUrl;
@SerializedName ("contents_url" )
@Expose
private String contentsUrl;
@SerializedName ("compare_url" )
@Expose
private String compareUrl;
@SerializedName ("merges_url" )
@Expose
private String mergesUrl;
@SerializedName ("archive_url" )
@Expose
private String archiveUrl;
@SerializedName ("downloads_url" )
@Expose
private String downloadsUrl;
@SerializedName ("issues_url" )
@Expose
private String issuesUrl;
@SerializedName ("pulls_url" )
@Expose
private String pullsUrl;
@SerializedName ("milestones_url" )
@Expose
private String milestonesUrl;
@SerializedName ("notifications_url" )
@Expose
private String notificationsUrl;
@SerializedName ("labels_url" )
@Expose
private String labelsUrl;
@SerializedName ("releases_url" )
@Expose
private String releasesUrl;
@SerializedName ("deployments_url" )
@Expose
private String deploymentsUrl;
@SerializedName ("created_at" )
@Expose
private String createdAt;
@SerializedName ("updated_at" )
@Expose
private String updatedAt;
@SerializedName ("pushed_at" )
@Expose
private String pushedAt;
@SerializedName ("git_url" )
@Expose
private String gitUrl;
@SerializedName ("ssh_url" )
@Expose
private String sshUrl;
@SerializedName ("clone_url" )
@Expose
private String cloneUrl;
@SerializedName ("svn_url" )
@Expose
private String svnUrl;
@SerializedName ("homepage" )
@Expose
private String homepage;
@SerializedName ("size" )
@Expose
private Integer size;
@SerializedName ("stargazers_count" )
@Expose
private Integer stargazersCount;
@SerializedName ("watchers_count" )
@Expose
private Integer watchersCount;
@SerializedName ("language" )
@Expose
private String language;
@SerializedName ("has_issues" )
@Expose
private Boolean hasIssues;
@SerializedName ("has_projects" )
@Expose
private Boolean hasProjects;
@SerializedName ("has_downloads" )
@Expose
private Boolean hasDownloads;
@SerializedName ("has_wiki" )
@Expose
private Boolean hasWiki;
@SerializedName ("has_pages" )
@Expose
private Boolean hasPages;
@SerializedName ("forks_count" )
@Expose
private Integer forksCount;
@SerializedName ("mirror_url" )
@Expose
private Object mirrorUrl;
@SerializedName ("open_issues_count" )
@Expose
private Integer openIssuesCount;
@SerializedName ("forks" )
@Expose
private Integer forks;
@SerializedName ("open_issues" )
@Expose
private Integer openIssues;
@SerializedName ("watchers" )
@Expose
private Integer watchers;
@SerializedName ("default_branch" )
@Expose
private String defaultBranch;
@SerializedName ("score" )
@Expose
private Double score;
public Integer getId () {
return id;
}
public void setId (Integer id) {
this .id = id;
}
public String getName () {
return name;
}
public void setName (String name) {
this .name = name;
}
public String getFullName () {
return fullName;
}
public void setFullName (String fullName) {
this .fullName = fullName;
}
public GithubOwnerModel getOwner () {
return owner;
}
public void setOwner (GithubOwnerModel owner) {
this .owner = owner;
}
public Boolean getPrivate () {
return _private;
}
public void setPrivate (Boolean _private) {
this ._private = _private;
}
public String getHtmlUrl () {
return htmlUrl;
}
public void setHtmlUrl (String htmlUrl) {
this .htmlUrl = htmlUrl;
}
public String getDescription () {
return description;
}
public void setDescription (String description) {
this .description = description;
}
public Boolean getFork () {
return fork;
}
public void setFork (Boolean fork) {
this .fork = fork;
}
public String getUrl () {
return url;
}
public void setUrl (String url) {
this .url = url;
}
public String getForksUrl () {
return forksUrl;
}
public void setForksUrl (String forksUrl) {
this .forksUrl = forksUrl;
}
public String getKeysUrl () {
return keysUrl;
}
public void setKeysUrl (String keysUrl) {
this .keysUrl = keysUrl;
}
public String getCollaboratorsUrl () {
return collaboratorsUrl;
}
public void setCollaboratorsUrl (String collaboratorsUrl) {
this .collaboratorsUrl = collaboratorsUrl;
}
public String getTeamsUrl () {
return teamsUrl;
}
public void setTeamsUrl (String teamsUrl) {
this .teamsUrl = teamsUrl;
}
public String getHooksUrl () {
return hooksUrl;
}
public void setHooksUrl (String hooksUrl) {
this .hooksUrl = hooksUrl;
}
public String getIssueEventsUrl () {
return issueEventsUrl;
}
public void setIssueEventsUrl (String issueEventsUrl) {
this .issueEventsUrl = issueEventsUrl;
}
public String getEventsUrl () {
return eventsUrl;
}
public void setEventsUrl (String eventsUrl) {
this .eventsUrl = eventsUrl;
}
public String getAssigneesUrl () {
return assigneesUrl;
}
public void setAssigneesUrl (String assigneesUrl) {
this .assigneesUrl = assigneesUrl;
}
public String getBranchesUrl () {
return branchesUrl;
}
public void setBranchesUrl (String branchesUrl) {
this .branchesUrl = branchesUrl;
}
public String getTagsUrl () {
return tagsUrl;
}
public void setTagsUrl (String tagsUrl) {
this .tagsUrl = tagsUrl;
}
public String getBlobsUrl () {
return blobsUrl;
}
public void setBlobsUrl (String blobsUrl) {
this .blobsUrl = blobsUrl;
}
public String getGitTagsUrl () {
return gitTagsUrl;
}
public void setGitTagsUrl (String gitTagsUrl) {
this .gitTagsUrl = gitTagsUrl;
}
public String getGitRefsUrl () {
return gitRefsUrl;
}
public void setGitRefsUrl (String gitRefsUrl) {
this .gitRefsUrl = gitRefsUrl;
}
public String getTreesUrl () {
return treesUrl;
}
public void setTreesUrl (String treesUrl) {
this .treesUrl = treesUrl;
}
public String getStatusesUrl () {
return statusesUrl;
}
public void setStatusesUrl (String statusesUrl) {
this .statusesUrl = statusesUrl;
}
public String getLanguagesUrl () {
return languagesUrl;
}
public void setLanguagesUrl (String languagesUrl) {
this .languagesUrl = languagesUrl;
}
public String getStargazersUrl () {
return stargazersUrl;
}
public void setStargazersUrl (String stargazersUrl) {
this .stargazersUrl = stargazersUrl;
}
public String getContributorsUrl () {
return contributorsUrl;
}
public void setContributorsUrl (String contributorsUrl) {
this .contributorsUrl = contributorsUrl;
}
public String getSubscribersUrl () {
return subscribersUrl;
}
public void setSubscribersUrl (String subscribersUrl) {
this .subscribersUrl = subscribersUrl;
}
public String getSubscriptionUrl () {
return subscriptionUrl;
}
public void setSubscriptionUrl (String subscriptionUrl) {
this .subscriptionUrl = subscriptionUrl;
}
public String getCommitsUrl () {
return commitsUrl;
}
public void setCommitsUrl (String commitsUrl) {
this .commitsUrl = commitsUrl;
}
public String getGitCommitsUrl () {
return gitCommitsUrl;
}
public void setGitCommitsUrl (String gitCommitsUrl) {
this .gitCommitsUrl = gitCommitsUrl;
}
public String getCommentsUrl () {
return commentsUrl;
}
public void setCommentsUrl (String commentsUrl) {
this .commentsUrl = commentsUrl;
}
public String getIssueCommentUrl () {
return issueCommentUrl;
}
public void setIssueCommentUrl (String issueCommentUrl) {
this .issueCommentUrl = issueCommentUrl;
}
public String getContentsUrl () {
return contentsUrl;
}
public void setContentsUrl (String contentsUrl) {
this .contentsUrl = contentsUrl;
}
public String getCompareUrl () {
return compareUrl;
}
public void setCompareUrl (String compareUrl) {
this .compareUrl = compareUrl;
}
public String getMergesUrl () {
return mergesUrl;
}
public void setMergesUrl (String mergesUrl) {
this .mergesUrl = mergesUrl;
}
public String getArchiveUrl () {
return archiveUrl;
}
public void setArchiveUrl (String archiveUrl) {
this .archiveUrl = archiveUrl;
}
public String getDownloadsUrl () {
return downloadsUrl;
}
public void setDownloadsUrl (String downloadsUrl) {
this .downloadsUrl = downloadsUrl;
}
public String getIssuesUrl () {
return issuesUrl;
}
public void setIssuesUrl (String issuesUrl) {
this .issuesUrl = issuesUrl;
}
public String getPullsUrl () {
return pullsUrl;
}
public void setPullsUrl (String pullsUrl) {
this .pullsUrl = pullsUrl;
}
public String getMilestonesUrl () {
return milestonesUrl;
}
public void setMilestonesUrl (String milestonesUrl) {
this .milestonesUrl = milestonesUrl;
}
public String getNotificationsUrl () {
return notificationsUrl;
}
public void setNotificationsUrl (String notificationsUrl) {
this .notificationsUrl = notificationsUrl;
}
public String getLabelsUrl () {
return labelsUrl;
}
public void setLabelsUrl (String labelsUrl) {
this .labelsUrl = labelsUrl;
}
public String getReleasesUrl () {
return releasesUrl;
}
public void setReleasesUrl (String releasesUrl) {
this .releasesUrl = releasesUrl;
}
public String getDeploymentsUrl () {
return deploymentsUrl;
}
public void setDeploymentsUrl (String deploymentsUrl) {
this .deploymentsUrl = deploymentsUrl;
}
public String getCreatedAt () {
return createdAt;
}
public void setCreatedAt (String createdAt) {
this .createdAt = createdAt;
}
public String getUpdatedAt () {
return updatedAt;
}
public void setUpdatedAt (String updatedAt) {
this .updatedAt = updatedAt;
}
public String getPushedAt () {
return pushedAt;
}
public void setPushedAt (String pushedAt) {
this .pushedAt = pushedAt;
}
public String getGitUrl () {
return gitUrl;
}
public void setGitUrl (String gitUrl) {
this .gitUrl = gitUrl;
}
public String getSshUrl () {
return sshUrl;
}
public void setSshUrl (String sshUrl) {
this .sshUrl = sshUrl;
}
public String getCloneUrl () {
return cloneUrl;
}
public void setCloneUrl (String cloneUrl) {
this .cloneUrl = cloneUrl;
}
public String getSvnUrl () {
return svnUrl;
}
public void setSvnUrl (String svnUrl) {
this .svnUrl = svnUrl;
}
public String getHomepage () {
return homepage;
}
public void setHomepage (String homepage) {
this .homepage = homepage;
}
public Integer getSize () {
return size;
}
public void setSize (Integer size) {
this .size = size;
}
public Integer getStargazersCount () {
return stargazersCount;
}
public void setStargazersCount (Integer stargazersCount) {
this .stargazersCount = stargazersCount;
}
public Integer getWatchersCount () {
return watchersCount;
}
public void setWatchersCount (Integer watchersCount) {
this .watchersCount = watchersCount;
}
public String getLanguage () {
return language;
}
public void setLanguage (String language) {
this .language = language;
}
public Boolean getHasIssues () {
return hasIssues;
}
public void setHasIssues (Boolean hasIssues) {
this .hasIssues = hasIssues;
}
public Boolean getHasProjects () {
return hasProjects;
}
public void setHasProjects (Boolean hasProjects) {
this .hasProjects = hasProjects;
}
public Boolean getHasDownloads () {
return hasDownloads;
}
public void setHasDownloads (Boolean hasDownloads) {
this .hasDownloads = hasDownloads;
}
public Boolean getHasWiki () {
return hasWiki;
}
public void setHasWiki (Boolean hasWiki) {
this .hasWiki = hasWiki;
}
public Boolean getHasPages () {
return hasPages;
}
public void setHasPages (Boolean hasPages) {
this .hasPages = hasPages;
}
public Integer getForksCount () {
return forksCount;
}
public void setForksCount (Integer forksCount) {
this .forksCount = forksCount;
}
public Object getMirrorUrl () {
return mirrorUrl;
}
public void setMirrorUrl (Object mirrorUrl) {
this .mirrorUrl = mirrorUrl;
}
public Integer getOpenIssuesCount () {
return openIssuesCount;
}
public void setOpenIssuesCount (Integer openIssuesCount) {
this .openIssuesCount = openIssuesCount;
}
public Integer getForks () {
return forks;
}
public void setForks (Integer forks) {
this .forks = forks;
}
public Integer getOpenIssues () {
return openIssues;
}
public void setOpenIssues (Integer openIssues) {
this .openIssues = openIssues;
}
public Integer getWatchers () {
return watchers;
}
public void setWatchers (Integer watchers) {
this .watchers = watchers;
}
public String getDefaultBranch () {
return defaultBranch;
}
public void setDefaultBranch (String defaultBranch) {
this .defaultBranch = defaultBranch;
}
public Double getScore () {
return score;
}
public void setScore (Double score) {
this .score = score;
}
}
GithubOwnerModel.java
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class GithubOwnerModel {
@SerializedName ("login" )
@Expose
private String login;
@SerializedName ("id" )
@Expose
private Integer id;
@SerializedName ("avatar_url" )
@Expose
private String avatarUrl;
@SerializedName ("gravatar_id" )
@Expose
private String gravatarId;
@SerializedName ("url" )
@Expose
private String url;
@SerializedName ("html_url" )
@Expose
private String htmlUrl;
@SerializedName ("followers_url" )
@Expose
private String followersUrl;
@SerializedName ("following_url" )
@Expose
private String followingUrl;
@SerializedName ("gists_url" )
@Expose
private String gistsUrl;
@SerializedName ("starred_url" )
@Expose
private String starredUrl;
@SerializedName ("subscriptions_url" )
@Expose
private String subscriptionsUrl;
@SerializedName ("organizations_url" )
@Expose
private String organizationsUrl;
@SerializedName ("repos_url" )
@Expose
private String reposUrl;
@SerializedName ("events_url" )
@Expose
private String eventsUrl;
@SerializedName ("received_events_url" )
@Expose
private String receivedEventsUrl;
@SerializedName ("type" )
@Expose
private String type;
@SerializedName ("site_admin" )
@Expose
private Boolean siteAdmin;
public String getLogin () {
return login;
}
public void setLogin (String login) {
this .login = login;
}
public Integer getId () {
return id;
}
public void setId (Integer id) {
this .id = id;
}
public String getAvatarUrl () {
return avatarUrl;
}
public void setAvatarUrl (String avatarUrl) {
this .avatarUrl = avatarUrl;
}
public String getGravatarId () {
return gravatarId;
}
public void setGravatarId (String gravatarId) {
this .gravatarId = gravatarId;
}
public String getUrl () {
return url;
}
public void setUrl (String url) {
this .url = url;
}
public String getHtmlUrl () {
return htmlUrl;
}
public void setHtmlUrl (String htmlUrl) {
this .htmlUrl = htmlUrl;
}
public String getFollowersUrl () {
return followersUrl;
}
public void setFollowersUrl (String followersUrl) {
this .followersUrl = followersUrl;
}
public String getFollowingUrl () {
return followingUrl;
}
public void setFollowingUrl (String followingUrl) {
this .followingUrl = followingUrl;
}
public String getGistsUrl () {
return gistsUrl;
}
public void setGistsUrl (String gistsUrl) {
this .gistsUrl = gistsUrl;
}
public String getStarredUrl () {
return starredUrl;
}
public void setStarredUrl (String starredUrl) {
this .starredUrl = starredUrl;
}
public String getSubscriptionsUrl () {
return subscriptionsUrl;
}
public void setSubscriptionsUrl (String subscriptionsUrl) {
this .subscriptionsUrl = subscriptionsUrl;
}
public String getOrganizationsUrl () {
return organizationsUrl;
}
public void setOrganizationsUrl (String organizationsUrl) {
this .organizationsUrl = organizationsUrl;
}
public String getReposUrl () {
return reposUrl;
}
public void setReposUrl (String reposUrl) {
this .reposUrl = reposUrl;
}
public String getEventsUrl () {
return eventsUrl;
}
public void setEventsUrl (String eventsUrl) {
this .eventsUrl = eventsUrl;
}
public String getReceivedEventsUrl () {
return receivedEventsUrl;
}
public void setReceivedEventsUrl (String receivedEventsUrl) {
this .receivedEventsUrl = receivedEventsUrl;
}
public String getType () {
return type;
}
public void setType (String type) {
this .type = type;
}
public Boolean getSiteAdmin () {
return siteAdmin;
}
public void setSiteAdmin (Boolean siteAdmin) {
this .siteAdmin = siteAdmin;
}
}
Теперь у нас есть наш файл который мы используем для парсинга json'a сразу после запроса. Дальше нам нужно создать клиент для создания запроса, в общем у нас это будет файл RestClient, в нем мы опишем работу с Retrofit и создадим нудные нам инстансы.
RestClient.java
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
public class RestClient {
public static final String BASE_URL = "https://api.github.com/" ;
private static final RestClient instance = new RestClient();
public static API instance () {
return instance.service;
}
private final API service;
public RestClient () {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(logLevel())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
service = retrofit.create(API.class);
}
private static OkHttpClient logLevel () {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
return client;
}
}
Этот кусок я уже описывал в статье про Retrofit, он по сути не изменный, вообще, основной функционал тут у нас в конструкторе, мы создаем инстанс билдера для инициализации ретрофита, прописываем куда нам должен стучаться ретрофит, указываем что нам нужно логировать все запросы, испольщуем фабрику для конвертирования всего что возвращает сервер в модели и фабрика для конвертирования с помощью библиотеки GSON. Вот собственно и все что нам нужно знать из этого класса.
Дальше нам нужно создать наш собственный RecyclerView который будет уметь по скроллу вниз определять достали ли мы до конца списка или нет. Собственно этот кусок я нашел на просторах интернета, допилил немного и получилось очень даже неплохо, можно еще сделать что бы нам отображало прогрессбар пока идет запрос, но я не стал усложнять код, по этому у нас его нет. Но по сути возможностей море.
EndlessRecyclerView.java
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
public class EndlessRecyclerView extends RecyclerView {
int pastVisiblesItems, visibleItemCount, totalItemCount;
private WrapperLinearLayout mLayoutManager;
private Context mContext;
private OnLoadMoreListener onLoadMoreListener;
public EndlessRecyclerView (Context context) {
super (context);
mContext = context;
init();
}
public EndlessRecyclerView (Context context, @Nullable AttributeSet attrs) {
super (context, attrs);
mContext = context;
init();
}
public EndlessRecyclerView (Context context, @Nullable AttributeSet attrs, int defStyle) {
super (context, attrs, defStyle);
mContext = context;
init();
}
private void init () {
mLayoutManager = new WrapperLinearLayout(mContext,LinearLayoutManager.VERTICAL,false );
this .setLayoutManager(mLayoutManager);
this .setItemAnimator(new DefaultItemAnimator());
this .setHasFixedSize(true );
}
@Override
public void onScrolled (int dx, int dy) {
super .onScrolled(dx, dy);
if (dy > 0 ) {
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
Log.e("..." , "Call Load More !" );
if (onLoadMoreListener != null ) {
onLoadMoreListener.onLoadMore();
}
}
}
}
@Override
public void onScrollStateChanged (int state) {
super .onScrollStateChanged(state);
}
public void setOnLoadMoreListener (OnLoadMoreListener onLoadMoreListener) {
this .onLoadMoreListener = onLoadMoreListener;
}
public interface OnLoadMoreListener {
void onLoadMore () ;
}
}
В этом классе у нас главную роль играет метод onScrolled, который отслеживает позицию скролла в списке. В нем мы складываем все наши итемы которые у нас есть, а это visibleItems и pastItems и проверяем больше ли их или они равны, если так, — тогда вызываем колбек onLoadMore, а он уже в свою очередь в нашей активити будет делать запрос и сетить данные в адаптер.
Еще у нас нужно создать wraper для RecyclerView, иначе он у нас просто не будет виден на экране, я не знаю зачем такие сложности вообще нужны, но его нужно прописывать каждый раз когда мы используем RecyclerView, по этому я сделал его wraper'ом, он у нас по дефолту прописан будет.
WrapperLinearLayout.java
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
public class WrapperLinearLayout extends LinearLayoutManager
{
public WrapperLinearLayout (Context context, int orientation, boolean reverseLayout) {
super (context, orientation, reverseLayout);
}
@Override
public void onLayoutChildren (RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super .onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e("probe" , "meet a IOOBE in RecyclerView" );
}
}
}
Далее нам нужно создать адаптер который у нас будет объеденять все эти классы вместе. Сперва создадим xml. В нем у нас будет два TextView в которых мы будем отображать имя репозитория и его описание.
item_repo.xml
<?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 ="wrap_content"
android:orientation ="vertical" >
<TextView
android:id ="@+id/name"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:text ="TextView"
android:textSize ="24sp" />
<TextView
android:id ="@+id/description"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:text ="TextView" />
</LinearLayout >
А далее создадим RecyclerView адаптер, он у нас кстати самый обыкновенный, в нем мы создаем холдер, в холдере мы описываем наши текствьюхи, а потом в onBindView заполняем их.
ReposRecycleViewAdapter.java
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.project.endlesslist.R;
import com.project.endlesslist.api.model.GithubItemModel;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
public class ReposRecycleViewAdapter extends RecyclerView .Adapter <ReposRecycleViewAdapter .ViewHolder > {
private List<GithubItemModel> githubItemModels = new ArrayList<>();
private Context context;
public ReposRecycleViewAdapter (Context context) {
this .context = context;
}
public void addItem (GithubItemModel githubItemModel) {
githubItemModels.add(githubItemModel);
}
@Override
public ReposRecycleViewAdapter.ViewHolder onCreateViewHolder (ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_repo, parent, false );
ReposRecycleViewAdapter.ViewHolder pvh = new ReposRecycleViewAdapter.ViewHolder(v);
return pvh;
}
@Override
public void onBindViewHolder (final ReposRecycleViewAdapter.ViewHolder holder, final int position) {
holder.name.setText(githubItemModels.get(position).getName());
holder.description.setText(githubItemModels.get(position).getDescription());
}
@Override
public int getItemCount () {
return githubItemModels.size();
}
public class ViewHolder extends RecyclerView .ViewHolder {
@BindView (R.id.name)
TextView name;
@BindView (R.id.description)
TextView description;
ViewHolder(final View itemView) {
super (itemView);
ButterKnife.bind(this , itemView);
}
}
}
Здесь нас интересует метод addItem, он по сути очень прост, в нем мы просто добавляем к нашему ArrayList'у то что нам возвращает сервер и обновляем список. Вот собственно и весь адаптер. Остальные методы стандартные для такого простого списка. В конструкторе мы передаем контекст, на всякий случай, а вдруг пригодится. :D Потом в onCreateViewHolder мы создаем холдер для работы с ним в onBindViewHolder, а уже в этом методе мы сетим данные с нашего списка который мы заполняем в addItem.
Дальше объеденяем всю эту херь в MainActivity и наконец-то закончим уже. Давайте сперва посмотрим как выглядит наш activity_main, а потом уже будем заполнять нашу активити.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:padding ="10dp" >
<LinearLayout
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:gravity ="center_vertical"
android:orientation ="horizontal" >
<EditText
android:id ="@+id/editText"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:layout_weight ="0.1"
android:ems ="10"
android:hint ="@string/search"
android:inputType ="textPersonName"
tools:layout_editor_absoluteX ="74dp"
tools:layout_editor_absoluteY ="-1dp" />
<Button
android:id ="@+id/button"
android:layout_width ="wrap_content"
android:layout_height ="wrap_content"
android:onClick ="onSearchClick"
android:text ="@string/search.button" />
</LinearLayout >
<com.project.endlesslist.view.EndlessRecyclerView
android:id ="@+id/recycleView"
android:layout_width ="match_parent"
android:layout_height ="match_parent" />
</LinearLayout >
Активити у нас будет состоять из поля для ввода, кнопки и самого списка. После нажатия на кнопку у нас будет происходить запрос на сервер, и далее будет отображаться все что нам вернул сервер в списке.
MainActivity.java
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.EditText;
import com.project.endlesslist.adapter.ReposRecycleViewAdapter;
import com.project.endlesslist.api.RestClient;
import com.project.endlesslist.api.model.GithubItemModel;
import com.project.endlesslist.api.model.GithubModel;
import com.project.endlesslist.view.EndlessRecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MainActivity extends AppCompatActivity implements EndlessRecyclerView .OnLoadMoreListener ,
Callback <GithubModel > {
private static final int MAX_ITEMS_PER_PAGE = 100 ;
@BindView (R.id.recycleView)
EndlessRecyclerView recyclerView;
@BindView (R.id.editText)
EditText editText;
ReposRecycleViewAdapter reposRecycleViewAdapter;
int currentPage = 0 ;
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this );
reposRecycleViewAdapter = new ReposRecycleViewAdapter(this );
recyclerView.setOnLoadMoreListener(this );
recyclerView.setAdapter(reposRecycleViewAdapter);
}
@OnClick (R.id.button)
public void onSearchClick () {
RestClient. instance().getSearchedRepos(editText.getText().toString(), currentPage, MAX_ITEMS_PER_PAGE).enqueue(this );
}
@Override
public void onLoadMore () {
currentPage++;
RestClient. instance().getSearchedRepos(editText.getText().toString(), currentPage, MAX_ITEMS_PER_PAGE).enqueue(this );
}
@Override
public void onResponse (Call<GithubModel> call, Response<GithubModel> response) {
GithubModel githubModel = response.body() != null ? response.body() : new GithubModel();
for (GithubItemModel model : githubModel.getItems())
reposRecycleViewAdapter.addItem(model);
reposRecycleViewAdapter.notifyDataSetChanged();
}
@Override
public void onFailure (Call<GithubModel> call, Throwable t) {
t.printStackTrace();
}
}
В общем что у нас тут происходит. В onCreate мы инициализируем, ButterKnife, адаптер и RecyclerView. Дальше в методе onSearchClick мы по клику делаем запрос в котором мы отправляем текст который у нас в edittext'e, какая по номеру у нас страница и максимальное количество айтемов мы хотим получать на каждый странице. В onLoadMore мы инкрементим currentPage для того что бы каждый раз у нас была новая страница, а не одна и та же, ну и опять делаем запрос с обновленными параметрами, а он у нас один обновляется, собственно наш currentPage. Ну, а потом у нас идет два метода, onResponse и onFailure, это два метода которые являются стандартными от Retrofit. onResponse — возвращает респонс с сервера, в этом методе мы сетим в цикле наши айтемы и обновляем список. А в onFailure нам возвращается ошибка с сервера, или же ошибка самого Retrofit'a. Там мы просто выводим стек трейс.
Ну вот собственно и все что нам нужно было, ниже можно увидеть видео как работает сейчас наш бесконечный список.
VIDEO
Исходники:
Комментариев нет:
Отправить комментарий