- 投稿日:2020-06-26T17:36:20+09:00
React Nativeで画面回転時のレイアウト崩れに対応したら、KeyboardAvoidingViewが効かなくなった
以下の2本立てです。
- React Nativeで画面回転したらAndroidの場合にレイアウトが崩れてしまったので対応した
- 今度はKeyboardAvoidingViewが効かなくなったので代替策で回避した
環境
- React Native 0.61.5
- iOS 13.5.1
- Android 10
React Nativeで画面回転したらレイアウト崩れが発生した
KeyboardAvoidingViewをrootに配置した画面をPortrait=>Landscape=>Portraitと回転させると、謎の空間が発生。AndroidでFlexが正しく動いていないような挙動をしました。
ソースはこのような感じです。
対応前class Hoge extends React.Component { render() { return ( <KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === 'ios' ? 'padding' : 'height'} > // ... </KeyboardAvoidingView> ); } );色々と解決方法を探して手当り次第試してみたところ、StackOverflowで見つけた方法で、stateを介して再renderさせるようにすることで正しくレイアウトされるようになりました。
対応後class Hoge extends React.Component { _onLayout = () => { this.setState({ width: Dimensions.get('window').width }); } render() { return ( <KeyboardAvoidingView onLayout={this._onLayout} style={{ flex: 1, width }} behavior={Platform.OS === 'ios' ? 'padding' : 'height'} > // ... </KeyboardAvoidingView> ); } );これにて一件落着・・と思いきや、、
今度はKeyboardAvoidingViewが効かなくなった
上記の対応を行うことで、iOS・Androidともに、KeyboardAvoidingViewがキーボードを避けないただのViewと化してしまいました。
Viewの入れ子関係を入れ替えたり、色々試してみたがうまくいかず。結局KeyboardAvoidingViewをあきらめ、代わりにKeyboardSpacerというライブラリを使うとうまくいきました。
対応後(改)class Hoge extends React.Component { _onLayout = ({nativeEvent}) => { const width = Dimensions.get('window').width; const height = Dimensions.get('window').height; const screenOffset = height - nativeEvent.layout.height; this.setState({ width, screenOffset }); }; render() { return ( <View onLayout={this._onLayout} style={{ flex: 1, width }} behavior={Platform.OS === 'ios' ? 'padding' : 'height'} > // ... <KeyboardSpacer topSpacing={(-1) * this.state.screenOffset} /> </View> ); } );はじめはAndroidで思うように機能しませんでしたが、AndroidManifestに
windowSoftInputMode="adjustResize"
を指定するとうまく動くようになりました。また、ReactNavigationのBottomTabNavigatorを使用しているため、WindowのサイズとViewのサイズの差分を計算し、
KeyboardSpacer
のtopSpacing
に設定しています。おわりに
画面回転つながりで、iPhoneでLandscapeにしたときにステータスバーが消える影響でレイアウトが崩れる問題も発生していたので、ステータスバーの高さをstoreで管理するようにしたりしました。
こういう小細工が色々と必要なのがツラいところです。そろそろ温泉にでも行きたいですねぇ。
- 投稿日:2020-06-26T03:29:47+09:00
【Android9.0 Pie Java】チャットアプリで横スワイプ→ダイアログ表示→削除を実装する
環境
Android9.0 Pie Java
はじめに
最近Android Javaでチャットアプリを作成していまして、LINE風のチャット一覧削除を実装しました。
RecyclerViewのリスト表示と削除機能の記事は結構ありますが、
横スワイプ→ダイアログ表示→削除
MainActivity + 複数のFragment構成の実装例は日本語の記事では見つからなかったので共有させて頂きます。
(ベストプラクティスかは怪しいので参考程度にお願いします。。ご指摘大歓迎です!)完成品URL
非常にコンテンツが長いので、完成品を取り合えず手に入れたい方はこちらからどうぞ。
https://github.com/yuta-matsumoto/chatディレクトリ構成
ビルドファイルやマニフェストファイルは省略します
Chat
├app/src/main/
├java/
│ └com.example.chat/
│ ├fragments/
│ │ ├BaseFragment.java
│ │ ├ChatListFragment.java
│ │ └DeleteChatFragment.java
│ ├helpers/
│ │ ├ChatListAdapter.java
│ │ ├ViewHolder.java
│ │ └SwipeHelper.java
│ ├models/
│ │ ├ChatRowData.java
│ │ └DeleteChatRow.java
│ └MainAcitivity.java
├res/
│ ├drawable/
│ │ └sample1.png(以下省略)
│ ├layout/
│ │ ├activity_main.xml
│ │ ├chat_list_row.xml
│ │ └fragment_chat_list.xml
│ └values/
│ ├colors.xml
│ ├strings.xml
│ └styles.xmlコード
依存関係
build.gradledependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.legacy:legacy-support-v4:1.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' // 追記 implementation 'androidx.cardview:cardview:1.0.0' // 追記 }Activity
MainActivityはFragmentを読み込むだけにしています。
FragmentをBaseFragmentという全てのFragmentのベースクラスを継承してMainActivity + 複数のFragment構成
を実現しています。
※チャット一覧画面→チャット画面のFragment切り替えで必要でした。
暇を見つけて続きのチャット画面の記事もその内共有したいと思います。以下の記事を参考にしています。
https://www.slideshare.net/olrandir/android-the-single-activity-multiple-fragments-pattern-one-activity-to-rule-them-allMainActivity.javapackage com.example.chat; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import com.example.chat.fragments.BaseFragment; import com.example.chat.fragments.ChatListFragment; public class MainActivity extends AppCompatActivity { // Fragmentのベース private BaseFragment fragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初回はチャット一覧のFragmentをセット if (fragment == null) { fragment = new ChatListFragment(); } // main_activityにFragmentをセット getSupportFragmentManager().beginTransaction() .replace(R.id.mainContainer, fragment) .commit(); } }Fragment
ベースとなるFragmentです。
今回は入れていませんが、共通となるボタンのイベントリスナー等をBaseFragmentに入れてしまうとコンパクトになるので便利です。BaseFragment.javapackage com.example.chat.fragments; import android.os.Bundle; import androidx.fragment.app.Fragment; /** * ベースとなるFragment */ public abstract class BaseFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } }チャットリスト画面のFragmentです。
ChatListFragment.javapackage com.example.chat.fragments; import android.graphics.Color; import android.os.Bundle; import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.example.chat.models.DeleteChatRowData; import com.example.chat.helpers.ChatListAdapter; import com.example.chat.models.ChatRowData; import com.example.chat.R; import com.example.chat.helpers.SwipeHelper; import java.util.ArrayList; import java.util.List; /** * チャット一覧画面用Fragment */ public class ChatListFragment extends BaseFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_chat_list, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); RecyclerView rv = view.findViewById(R.id.recyclerView); // チャット一覧のデータList final List list = getChatList(); // チャット一覧のデータListの要素数 final int itemCount = list.size(); // チャット一覧のアダプター final ChatListAdapter adapter = new ChatListAdapter(list) { @Override public void onItemClick(View view, int pos, List<ChatRowData> list) { // 行をクリックした時の処理を追記 } }; LinearLayoutManager llm = new LinearLayoutManager(getContext()); rv.setHasFixedSize(true); rv.setLayoutManager(llm); rv.setAdapter(adapter); // スワイプを実装 SwipeHelper swipeHelper = new SwipeHelper(getContext(), rv) { @Override public void instantiateUnderlayButton(RecyclerView.ViewHolder viewHolder, List<UnderlayButton> underlayButtons) { underlayButtons.add(new SwipeHelper.UnderlayButton( getResources().getString(R.string.chat_list_delete_button_label), 0, Color.parseColor(getResources().getString(R.string.chat_list_delete_button_color)), new SwipeHelper.UnderlayButtonClickListener() { @Override public void onClick(int pos) { FragmentManager fragmentManager = getFragmentManager(); DeleteChatRowFragment fragment = new DeleteChatRowFragment(); // 削除ダイアログfragmentに削除する行データをセット DeleteChatRowData deleteChatRowData = new DeleteChatRowData(); deleteChatRowData.setList(list); deleteChatRowData.setAdapter(adapter); deleteChatRowData.setPosition(pos); deleteChatRowData.setItemCount(itemCount); // bundleを利用してデータを渡す Bundle bundle = new Bundle(); bundle.putSerializable(getResources().getString(R.string.delete_dialog_list_tag), deleteChatRowData); fragment.setArguments(bundle); // ダイアログ表示 fragment.show(fragmentManager, "delete chat list"); } } )); } }; } /** * チャット一覧のテストデータ生成 */ private List<ChatRowData> getChatList() { List<ChatRowData> list = new ArrayList<>(); ChatRowData data1 = new ChatRowData(); data1.setName("田中太郎"); data1.setText("こんにちは"); data1.setMessageDateTime("2020/6/09 13:00"); data1.setProfileImageId(R.drawable.sample1); list.add(data1); ChatRowData data2 = new ChatRowData(); data2.setName("佐藤茂"); data2.setText("おはようございます!"); data2.setMessageDateTime("2020/6/08 8:10"); data2.setProfileImageId(R.drawable.sample2); list.add(data2); ChatRowData data3 = new ChatRowData(); data3.setName("taro"); data3.setText("何時だっけ?"); data3.setMessageDateTime("2020/6/07 20:09"); data3.setProfileImageId(R.drawable.sample3); list.add(data3); ChatRowData data4 = new ChatRowData(); data4.setName("hanako"); data4.setText("教科書を貸してください"); data4.setMessageDateTime("2020/6/06 07:00"); data4.setProfileImageId(R.drawable.sample4); list.add(data4); ChatRowData data5 = new ChatRowData(); data5.setName("たなか"); data5.setText("無理"); data5.setMessageDateTime("2020/6/06 01:05"); data5.setProfileImageId(R.drawable.sample5); list.add(data5); ChatRowData data6 = new ChatRowData(); data6.setName("小林"); data6.setText("いいよ"); data6.setMessageDateTime("2020/6/05 14:22"); data6.setProfileImageId(R.drawable.sample6); list.add(data6); ChatRowData data7 = new ChatRowData(); data7.setName("ペタジーニ"); data7.setText("帰りたい"); data7.setMessageDateTime("2020/6/05 13:00"); data7.setProfileImageId(R.drawable.sample7); list.add(data7); ChatRowData data8 = new ChatRowData(); data8.setName("Hayato"); data8.setText("映画を見に行きましょう先輩!"); data8.setMessageDateTime("2020/6/04 21:50"); data8.setProfileImageId(R.drawable.sample8); list.add(data8); ChatRowData data9 = new ChatRowData(); data9.setName("Tom"); data9.setText("lol"); data9.setMessageDateTime("2020/5/30 2:30"); data9.setProfileImageId(R.drawable.sample9); list.add(data9); ChatRowData data10 = new ChatRowData(); data10.setName("y.matsumoto"); data10.setText("やったぜ"); data10.setMessageDateTime("2020/5/29 4:00"); data10.setProfileImageId(R.drawable.sample10); list.add(data10); return list; } }削除ダイアログのFragmentです。
DeleteChatRowFragment.javapackage com.example.chat.fragments; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; import android.view.Gravity; import android.widget.TextView; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.recyclerview.widget.RecyclerView; import com.example.chat.R; import com.example.chat.models.DeleteChatRowData; import java.util.List; /** * 削除ダイアログ用Fragment */ public class DeleteChatRowFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // setCancelable(false)でダイアログ外を押しても閉じない this.setCancelable(false); TextView title = new TextView(getContext()); title.setText(getResources().getString(R.string.delete_dialog_message)); title.setPadding(10, 50, 10, 10); title.setGravity(Gravity.CENTER); return new AlertDialog.Builder(getActivity()) .setCustomTitle(title) // OKが押された場合 .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 削除処理 Bundle bundle = getArguments(); DeleteChatRowData deleteChatRowData = (DeleteChatRowData) bundle.getSerializable(getResources().getString(R.string.delete_dialog_list_tag)); List list = deleteChatRowData.getList(); RecyclerView.Adapter adapter = deleteChatRowData.getAdapter(); int pos = deleteChatRowData.getPosition(); int itemCount = deleteChatRowData.getItemCount(); // チャット一覧Listから押された行のpositionの順番の要素を削除 list.remove(pos); // アダプターに要素を削除したことを通知 deleteChatRowData.getAdapter().notifyItemRemoved(pos); // チャット一覧に変更があったことを通知しバインドし直す adapter.notifyItemRangeChanged(pos, itemCount); } }) // Cancelが押された場合 .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // スワイプを戻す Bundle bundle = getArguments(); DeleteChatRowData deleteChatRowData = (DeleteChatRowData) bundle.getSerializable(getResources().getString(R.string.delete_dialog_list_tag)); RecyclerView.Adapter adapter = deleteChatRowData.getAdapter(); int pos = deleteChatRowData.getPosition(); // スワイプが元に戻る adapter.notifyItemChanged(pos); } }) .create(); } // アプリがバックグラウンドに回った時終了させない @Override public void onPause() { super.onPause(); dismiss(); } }ヘルパー
チャットの一覧表示で使用します。
ChatListAdapter.javapackage com.example.chat.helpers; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import androidx.recyclerview.widget.RecyclerView; import com.example.chat.R; import com.example.chat.models.ChatRowData; import java.util.List; /** * チャット一覧表示に使用するAdapterクラス */ public class ChatListAdapter extends RecyclerView.Adapter<ViewHolder> { private List<ChatRowData> list; public ChatListAdapter(List<ChatRowData> list) { this.list = list; } /** * チャット一覧のViewHolderを作成する */ @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // 一行分のlayoutをViewに読み込む View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_list_row, parent, false); final ViewHolder vh = new ViewHolder(inflate); // クリックリスナーを登録 inflate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // クリックされた行のpositionを取得 int position = vh.getAdapterPosition(); // Viewの操作はActivityかFragmentでハンドリングしなくてはいけないので実処理は書かない onItemClick(v, position, list); } }); // タッチリスナーを登録 inflate.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // Viewの操作はActivityかFragmentでハンドリングしなくてはいけないので実処理は書かない return onItemTouch(v); } }); return vh; } /** * ViewHolder内のViewにチャット一覧Listのデータをbindする */ @Override public void onBindViewHolder(ViewHolder holder, int position) { String messageDateTime = list.get(position).getMessageDateTime(); holder.nameView.setText(list.get(position).getName()); holder.textView.setText(list.get(position).getText()); holder.timeView.setText(messageDateTime); holder.profileView.setImageResource(list.get(position).getProfileImageId()); } /** * チャット一覧Listの要素数を設定する */ @Override public int getItemCount() { return list.size(); } /** * ChatListFragmentでoverrideして処理させる */ public void onItemClick(View view, int pos, List<ChatRowData> list) { ; } /** * ChatListFragmentでoverrideして処理させる */ public boolean onItemTouch(View view) { return false; } }ViewHolder.javapackage com.example.chat.helpers; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; import com.example.chat.R; /** * ViewHolderクラス * 一行分を構成するViewを定義しておく */ public class ViewHolder extends RecyclerView.ViewHolder { public TextView nameView; public TextView textView; public TextView timeView; public ImageView profileView; public ViewHolder(View itemView) { super(itemView); nameView = itemView.findViewById(R.id.name); textView = itemView.findViewById(R.id.text); timeView = itemView.findViewById(R.id.time); profileView = itemView.findViewById(R.id.profileImage); } }横スワイプの動きで使用します。
こちらの記事をかなり参考にしました。SwipeHelper.javapackage com.example.chat.helpers; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; /** * スワイプのヘルパークラス */ public abstract class SwipeHelper extends ItemTouchHelper.SimpleCallback { // スワイプで表示されるDELETEボタンのwidth public static final int BUTTON_WIDTH = 230; private RecyclerView recyclerView; private List<UnderlayButton> buttons; private GestureDetector gestureDetector; private int swipedPos = -1; private float swipeThreshold = 0.5f; private Map<Integer, List<UnderlayButton>> buttonsBuffer; private Queue<Integer> recoverQueue; private GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapConfirmed(MotionEvent e) { for (UnderlayButton button : buttons) { if (button.onClick(e.getX(), e.getY())) break; } return true; } }; private View.OnTouchListener onTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent e) { if (swipedPos < 0) return false; Point point = new Point((int) e.getRawX(), (int) e.getRawY()); RecyclerView.ViewHolder swipedViewHolder = recyclerView.findViewHolderForAdapterPosition(swipedPos); View swipedItem = swipedViewHolder.itemView; Rect rect = new Rect(); swipedItem.getGlobalVisibleRect(rect); if (e.getAction() == MotionEvent.ACTION_DOWN || e.getAction() == MotionEvent.ACTION_UP || e.getAction() == MotionEvent.ACTION_MOVE) { if (rect.top < point.y && rect.bottom > point.y) gestureDetector.onTouchEvent(e); else { recoverQueue.add(swipedPos); swipedPos = -1; recoverSwipedItem(); } } return false; } }; public SwipeHelper(Context context, RecyclerView recyclerView) { super(0, ItemTouchHelper.LEFT); this.recyclerView = recyclerView; this.buttons = new ArrayList<>(); this.gestureDetector = new GestureDetector(context, gestureListener); this.recyclerView.setOnTouchListener(onTouchListener); buttonsBuffer = new HashMap<>(); recoverQueue = new LinkedList<Integer>() { @Override public boolean add(Integer o) { if (contains(o)) return false; else return super.add(o); } }; attachSwipe(); } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { int pos = viewHolder.getAdapterPosition(); if (swipedPos != pos) recoverQueue.add(swipedPos); swipedPos = pos; if (buttonsBuffer.containsKey(swipedPos)) buttons = buttonsBuffer.get(swipedPos); else buttons.clear(); buttonsBuffer.clear(); swipeThreshold = 0.5f * buttons.size() * BUTTON_WIDTH; recoverSwipedItem(); } @Override public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) { return swipeThreshold; } @Override public float getSwipeEscapeVelocity(float defaultValue) { return 0.1f * defaultValue; } @Override public float getSwipeVelocityThreshold(float defaultValue) { return 5.0f * defaultValue; } @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { int pos = viewHolder.getAdapterPosition(); float translationX = dX; View itemView = viewHolder.itemView; if (pos < 0) { swipedPos = pos; return; } if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { if (dX < 0) { List<UnderlayButton> buffer = new ArrayList<>(); if (!buttonsBuffer.containsKey(pos)) { instantiateUnderlayButton(viewHolder, buffer); buttonsBuffer.put(pos, buffer); } else { buffer = buttonsBuffer.get(pos); } translationX = dX * buffer.size() * BUTTON_WIDTH / itemView.getWidth(); drawButtons(c, itemView, buffer, pos, translationX); } } super.onChildDraw(c, recyclerView, viewHolder, translationX, dY, actionState, isCurrentlyActive); } private synchronized void recoverSwipedItem() { while (!recoverQueue.isEmpty()) { int pos = recoverQueue.poll(); if (pos > -1) { recyclerView.getAdapter().notifyItemChanged(pos); } } } private void drawButtons(Canvas c, View itemView, List<UnderlayButton> buffer, int pos, float dX) { float right = itemView.getRight(); float dButtonWidth = (-1) * dX / buffer.size(); for (UnderlayButton button : buffer) { float left = right - dButtonWidth; button.onDraw( c, new RectF( left, itemView.getTop(), right, itemView.getBottom() ), pos ); right = left; } } public void attachSwipe() { ItemTouchHelper itemTouchHelper = new ItemTouchHelper(this); itemTouchHelper.attachToRecyclerView(recyclerView); } public abstract void instantiateUnderlayButton(RecyclerView.ViewHolder viewHolder, List<UnderlayButton> underlayButtons); public static class UnderlayButton { private String text; private int imageResId; private int color; private int pos; private RectF clickRegion; private UnderlayButtonClickListener clickListener; public UnderlayButton(String text, int imageResId, int color, UnderlayButtonClickListener clickListener) { this.text = text; this.imageResId = imageResId; this.color = color; this.clickListener = clickListener; } public boolean onClick(float x, float y) { if (clickRegion != null && clickRegion.contains(x, y)) { clickListener.onClick(pos); return true; } return false; } public void onDraw(Canvas c, RectF rect, int pos) { Paint p = new Paint(); // 背景色セット p.setColor(color); c.drawRect(rect, p); // DELETEの文字色セット p.setColor(Color.WHITE); p.setTextSize(50); Rect r = new Rect(); float cHeight = rect.height(); float cWidth = rect.width(); p.setTextAlign(Paint.Align.LEFT); p.getTextBounds(text, 0, text.length(), r); float x = cWidth / 2f - r.width() / 2f - r.left; float y = cHeight / 2f + r.height() / 2f - r.bottom; c.drawText(text, rect.left + x, rect.top + y, p); clickRegion = rect; this.pos = pos; } } public interface UnderlayButtonClickListener { void onClick(int pos); } }モデル
チャット一覧の一行分のデータを詰めます。
ChatRowData.javapackage com.example.chat.models; /** * 一行分のデータモデルクラス */ public class ChatRowData { private String name; private String text; private String messageDateTime; private int profileImageId; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getMessageDateTime() { return messageDateTime; } public void setMessageDateTime(String messageDateTime) { this.messageDateTime = messageDateTime; } public int getProfileImageId() { return profileImageId; } public void setProfileImageId(int profileImageId) { this.profileImageId = profileImageId; } }削除ダイアログ用に必要なデータを詰めます。
ChatRowData.javapackage com.example.chat.models; /** * 一行分のデータモデルクラス */ public class ChatRowData { private String name; private String text; private String messageDateTime; private int profileImageId; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getMessageDateTime() { return messageDateTime; } public void setMessageDateTime(String messageDateTime) { this.messageDateTime = messageDateTime; } public int getProfileImageId() { return profileImageId; } public void setProfileImageId(int profileImageId) { this.profileImageId = profileImageId; } }レイアウト
activity_mainにはFragmentを入れるためのFrameLayout以外は記述しません。
本当はヘッダーとフッターも共通パーツ化してしまえばメンテナンス性も向上するのですが、・追加予定のチャット画面への遷移アニメーション(右から画面全体にチャット画面のレイヤーが覆い被さる感じ)が中々困難になる点
・バックキーの挙動を組み合わせると地獄に陥った点から共通パーツ化は見送りました。。
次回の記事にて有識者の方の改善策がもし聞けたら本当に嬉しいです。。activity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:focusableInTouchMode="true" tools:context=".MainActivity"> <FrameLayout android:id="@+id/mainContainer" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> </FrameLayout> </androidx.constraintlayout.widget.ConstraintLayout>チャット一覧画面用のfragment layoutです。
fragment_chat_list.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" android:background="@color/background_light_gray" tools:context=".fragments.ChatListFragment"> <View android:id="@+id/headerView" android:layout_width="wrap_content" android:layout_height="50dp" android:background="@color/background_dark_gray" android:contextClickable="false" android:layerType="none" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/chatListSubject" android:layout_width="63dp" android:layout_height="19dp" android:text="@string/chat_list_subject_label" android:textAlignment="center" android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textColor="@color/font_color_black" app:layout_constraintBottom_toBottomOf="@+id/headerView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/headerView" app:layout_constraintVertical_bias="0.48" /> <View android:id="@+id/headerBorder" android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_marginTop="48dp" android:background="@color/background_border" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/headerView" /> <LinearLayout android:layout_width="0dp" android:layout_height="0dp" android:isScrollContainer="false" app:layout_constraintBottom_toTopOf="@+id/footerBorder" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/headerView" app:layout_constraintVertical_bias="0.0"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> </LinearLayout> <View android:id="@+id/background" android:layout_width="wrap_content" android:layout_height="0dp" android:background="@color/background_dark_gray" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/footerBorder" /> <ImageButton android:id="@+id/homeButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="80dp" android:background="@color/background_transparent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/footerBorder" app:srcCompat="@drawable/home" /> <ImageButton android:id="@+id/chatListButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/background_transparent" app:layout_constraintEnd_toStartOf="@+id/userButton" app:layout_constraintStart_toEndOf="@+id/homeButton" app:layout_constraintTop_toTopOf="@+id/homeButton" app:srcCompat="@drawable/fukidashi" /> <ImageButton android:id="@+id/userButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="80dp" android:background="@color/background_transparent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/chatListButton" app:srcCompat="@drawable/person" /> <View android:id="@+id/footerBorder" android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_marginBottom="72dp" android:background="@color/background_border" 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>チャット一覧一行分のlayoutです。
chat_list_row.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" android:layout_width="match_parent" android:layout_height="wrap_content"> <androidx.cardview.widget.CardView android:id="@+id/chatListCardView" android:layout_width="50dp" android:layout_height="50dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:cardCornerRadius="15dp" app:cardElevation="0dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <ImageView android:id="@+id/profileImage" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@mipmap/ic_launcher" /> </androidx.cardview.widget.CardView> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="0dp" android:layout_marginStart="16dp" android:text="name" android:textColor="@color/font_color_black" android:textSize="12sp" android:textStyle="bold" app:layout_constraintStart_toEndOf="@+id/chatListCardView" app:layout_constraintTop_toTopOf="@+id/chatListCardView" /> <TextView android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:text="time" android:textColor="@color/font_color_black" android:textSize="8sp" app:layout_constraintBottom_toBottomOf="@+id/name" app:layout_constraintStart_toEndOf="@+id/name" /> <TextView android:id="@+id/text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginEnd="16dp" android:layout_marginBottom="8dp" android:text="text" android:textColor="@color/font_color_black" android:textSize="12sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/name" app:layout_constraintTop_toBottomOf="@+id/name" /> <View android:id="@+id/line" android:layout_width="match_parent" android:layout_height="0.3dp" android:layout_marginTop="8dp" android:background="@color/background_border" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/chatListCardView" /> </androidx.constraintlayout.widget.ConstraintLayout>values
使用する定数ファイルです
colors.xml<?xml version="1.0" encoding="utf-8"?> <resources> <!-- フォントの色 黒 --> <color name="font_color_black">#5E5E5E</color> <!-- フォント オレンジ --> <color name="font_color_orange">#FF9900</color> <!-- background body --> <color name="background_light_gray">#EBEBEB</color> <!-- background header, footer --> <color name="background_dark_gray">#E5E5E5</color> <!-- background 区切り線 --> <color name="background_border">#838383</color> <!-- background 透過 --> <color name="background_transparent">#00E5E5E5</color> </resources>strings.xml<?xml version="1.0" encoding="utf-8"?> <!-- 定数管理ファイル --> <resources> <!-- アプリ名 --> <string name="app_name">Chat</string> <!-- チャット一覧画面 --> <string name="chat_list_subject_label">CHATS</string> <string name="chat_list_delete_button_color">#FF9900</string> <string name="chat_list_delete_button_label">DELETE</string> <string name="delete_dialog_message">このチャット履歴を削除しても\nよろしいでしょうか?</string> <string name="delete_dialog_list_tag">chatList</string> </resources>styles.xml<?xml version="1.0" encoding="utf-8"?> <resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorAccent">@color/font_color_orange</item> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> </resources>以上のコードで冒頭のサンプルの動きが再現できると思います。
こちらにソースも上がっていますので宜しければご確認ください。
https://github.com/yuta-matsumoto/chat最後に
ハンズオン系の記事を初めて書きましたが、どこまで説明すべきか非常に戸惑いますね。。
もしどなたかの助けになれましたらとても嬉しいです!
- 投稿日:2020-06-26T01:40:55+09:00
[Android] Waiting For Debuggerが終わらない時の打開策?
困って調べたのでメモ代わりに載せます
何のこと?
デバッグ中に起こる
Waiting For Debugger Application is waiting for the debugger to attach.解決方法
エミュレーター側で、
- Settings(設定) -> About emulated device(エミュレートされたデバイスについて) -> Build number(ビルド番号) を7回タップ →Developer options(開発者向けオプション)が有効になる
- Settings(設定) -> System(システム) -> Advanced(詳細設定) -> Developer options(開発者向けオプション) -> Select debug app(デバッグアプリを選択)を開き、自分のアプリを選択する
実機でも一緒だと思います。
調査時の環境:Android Studio 4.0 / Android 10
- 投稿日:2020-06-26T01:12:24+09:00
MacでゼロからのFlutter環境構築
ゼロからFlutterの開発環境を構築するには以下の3つの手順が必要です。
- Flutterを導入する
- Xcodeを設定する
- Android Studioを設定する
Flutterを導入する
Flutterのインストール
Flutter公式からインストーラーをダウンロードします。
ダウンロードしたzip
を解答して、flutter
フォルダを任意の場所に置いてください。
私はアプリケーション直下に配置しました。コマンドの有効化
- flutterフォルダの中にある
flutter_console.bat
を実行します。 これでFlutter Consoleからflutterコマンドが使えるようになります。Pathを通す
~/.bashrc
または~/.bash_profile
に以下を記述する。$ export PATH=$PATH:/Applications/flutter/binXcodeを設定する
Xcodeのインストール
App Storeで
Xcode
を検索してインストールします。
Xcodeのインストールにはかなり時間がかかります。時間の余裕がある時にインストールをしておくのをおすすめします。Xcodeの設定を変更する
このコマンドを実装する。これでXcodeが使用できるようになる。
$ sudo xcode-select —switch /Applications/Xcode.app/Contents/Developerライセンスを表示して同意する。
コマンドから行ってもいいし、Xcodeを起動するでもOK。$ sudo xcodebuild -licenseAndroid Studio
Android Studioのインストール
App Storeで
Android Studio
を検索してインストールします。
こちらもなかなかお時間がかかりましたね。。Flutter Pluginのインストール
メニュー>Android Studio>Preference...
を選択する。
横のメニューから
Plugin
を選択。Flutter
を検索してインストールする。
※画像はインストール済み
Android SDK toolsを設定する
Android SDK tools
のパケージ名がAndroid-SDK command line tools
に変わっていて、flutterからAndroid SDKをみにいくのに失敗するようになっていました。
なのでこちらを設定します。先ほどと同様に
メニュー>Android Studio>Preference...
を選択します。
横のメニューのAndroid SDK
を選択した、SDK Tools
の一覧をみます。
こちらしたの方にあるHide Obsolete Packages
のチェックを外すとAndroid SDK tools
が一覧に出てくるのでインストールします。
動作確認
ターミナルで以下を実行。こんな感じで返ってくれば完了です。
No devices available
これはシミュレーターを起動していない時に出るので、実際作業するときはお忘れなく。$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, v1.12.13+hotfix.9, on Mac OS X 10.15.4 19E266, locale ja-JP) [✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3) [✓] Xcode - develop for iOS and macOS (Xcode 11.5) [✓] Android Studio (version 3.6) [!] Connected device ! No devices available ! Doctor found issues in 1 category.
- 投稿日:2020-06-26T00:13:07+09:00
Android + kotlin + ViewPager2 でカレンダーの横スクロールを実現する
概要
今回は前回作った6月のカレンダーをスクロールして、7,8月のカレンダーも見れるようにしたいと思います。
技術テーマ
- ViewPager2
横スクロールにはViewPagerは避けては通れないのでそちらを使って実装していこうと思います。
手順
Fragmentの作成
ViewPagerではスクロールすることでfragmentが切り替わるというものなので、今までActivityしか使ってなかったのですが、メインの処理をFragmentに移行したいと思います。
fragment_calendar.xml<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"> <data> <variable name="date" type="String[]" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <!--縦分割のガイドライン--> <androidx.constraintlayout.widget.Guideline android:id="@+id/youbi" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.1" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/line_1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.25" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/line_2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.40" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/line_3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.55" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/line_4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.70" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/line_5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.85" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/line_6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="1" /> <!--横分割のガイドライン--> <androidx.constraintlayout.widget.Guideline android:id="@+id/row1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.14" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/row2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.28" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/row3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.42" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/row4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.56" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/row5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.70" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/row6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.84" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/row7" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="1" /> <TextView android:id="@+id/sunday" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_red" android:gravity="center_horizontal|center_vertical" android:text="Sun" app:layout_constraintBottom_toBottomOf="@id/youbi" app:layout_constraintEnd_toEndOf="@id/row1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/monday" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="Mon" app:layout_constraintBottom_toBottomOf="@id/youbi" app:layout_constraintEnd_toEndOf="@id/row2" app:layout_constraintStart_toEndOf="@+id/row1" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/tuesday" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="Tue" app:layout_constraintBottom_toBottomOf="@id/youbi" app:layout_constraintEnd_toEndOf="@id/row3" app:layout_constraintStart_toEndOf="@+id/row2" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/wednesday" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="Wed" app:layout_constraintBottom_toBottomOf="@id/youbi" app:layout_constraintEnd_toEndOf="@id/row4" app:layout_constraintStart_toEndOf="@+id/row3" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/thursday" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="Thu" app:layout_constraintBottom_toBottomOf="@id/youbi" app:layout_constraintEnd_toEndOf="@id/row5" app:layout_constraintStart_toEndOf="@+id/row4" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/friday" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="Fri" app:layout_constraintBottom_toBottomOf="@id/youbi" app:layout_constraintEnd_toEndOf="@id/row6" app:layout_constraintStart_toEndOf="@+id/row5" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/saturday" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_blue" android:gravity="center_horizontal|center_vertical" android:text="Sat" app:layout_constraintBottom_toBottomOf="@id/youbi" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/row6" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/cell_1" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_red" android:gravity="center_horizontal|center_vertical" android:text="@{date[0]}" app:layout_constraintBottom_toBottomOf="@id/line_1" app:layout_constraintEnd_toEndOf="@id/row1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/youbi" /> <TextView android:id="@+id/cell_2" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[1]}" app:layout_constraintBottom_toBottomOf="@id/line_1" app:layout_constraintEnd_toEndOf="@id/row2" app:layout_constraintStart_toEndOf="@+id/row1" app:layout_constraintTop_toTopOf="@id/youbi" /> <TextView android:id="@+id/cell_3" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[2]}" app:layout_constraintBottom_toBottomOf="@id/line_1" app:layout_constraintEnd_toEndOf="@id/row3" app:layout_constraintStart_toEndOf="@+id/row2" app:layout_constraintTop_toTopOf="@id/youbi" /> <TextView android:id="@+id/cell_4" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[3]}" app:layout_constraintBottom_toBottomOf="@id/line_1" app:layout_constraintEnd_toEndOf="@id/row4" app:layout_constraintStart_toEndOf="@+id/row3" app:layout_constraintTop_toTopOf="@id/youbi" /> <TextView android:id="@+id/cell_5" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[4]}" app:layout_constraintBottom_toBottomOf="@id/line_1" app:layout_constraintEnd_toEndOf="@id/row5" app:layout_constraintStart_toEndOf="@+id/row4" app:layout_constraintTop_toTopOf="@id/youbi" /> <TextView android:id="@+id/cell_6" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[5]}" app:layout_constraintBottom_toBottomOf="@id/line_1" app:layout_constraintEnd_toEndOf="@id/row6" app:layout_constraintStart_toEndOf="@+id/row5" app:layout_constraintTop_toTopOf="@id/youbi" /> <TextView android:id="@+id/cell_7" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_blue" android:gravity="center_horizontal|center_vertical" android:text="@{date[6]}" app:layout_constraintBottom_toBottomOf="@id/line_1" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/row6" app:layout_constraintTop_toTopOf="@id/youbi" /> <TextView android:id="@+id/cell_21" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_red" android:gravity="center_horizontal|center_vertical" android:text="@{date[7]}" app:layout_constraintBottom_toBottomOf="@id/line_2" app:layout_constraintEnd_toEndOf="@id/row1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/line_1" /> <TextView android:id="@+id/cell_22" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[8]}" app:layout_constraintBottom_toBottomOf="@id/line_2" app:layout_constraintEnd_toEndOf="@id/row2" app:layout_constraintStart_toEndOf="@+id/row1" app:layout_constraintTop_toTopOf="@id/line_1" /> <TextView android:id="@+id/cell_23" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[9]}" app:layout_constraintBottom_toBottomOf="@id/line_2" app:layout_constraintEnd_toEndOf="@id/row3" app:layout_constraintStart_toEndOf="@+id/row2" app:layout_constraintTop_toTopOf="@id/line_1" /> <TextView android:id="@+id/cell_24" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[10]}" app:layout_constraintBottom_toBottomOf="@id/line_2" app:layout_constraintEnd_toEndOf="@id/row4" app:layout_constraintStart_toEndOf="@+id/row3" app:layout_constraintTop_toTopOf="@id/line_1" /> <TextView android:id="@+id/cell_25" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[11]}" app:layout_constraintBottom_toBottomOf="@id/line_2" app:layout_constraintEnd_toEndOf="@id/row5" app:layout_constraintStart_toEndOf="@+id/row4" app:layout_constraintTop_toTopOf="@id/line_1" /> <TextView android:id="@+id/cell_26" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[12]}" app:layout_constraintBottom_toBottomOf="@id/line_2" app:layout_constraintEnd_toEndOf="@id/row6" app:layout_constraintStart_toEndOf="@+id/row5" app:layout_constraintTop_toTopOf="@id/line_1" /> <TextView android:id="@+id/cell_27" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_blue" android:gravity="center_horizontal|center_vertical" android:text="@{date[13]}" app:layout_constraintBottom_toBottomOf="@id/line_2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/row6" app:layout_constraintTop_toTopOf="@id/line_1" /> <TextView android:id="@+id/cell_31" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_red" android:gravity="center_horizontal|center_vertical" android:text="@{date[14]}" app:layout_constraintBottom_toBottomOf="@id/line_3" app:layout_constraintEnd_toEndOf="@id/row1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/line_2" /> <TextView android:id="@+id/cell_32" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[15]}" app:layout_constraintBottom_toBottomOf="@id/line_3" app:layout_constraintEnd_toEndOf="@id/row2" app:layout_constraintStart_toEndOf="@+id/row1" app:layout_constraintTop_toTopOf="@id/line_2" /> <TextView android:id="@+id/cell_33" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[16]}" app:layout_constraintBottom_toBottomOf="@id/line_3" app:layout_constraintEnd_toEndOf="@id/row3" app:layout_constraintStart_toEndOf="@+id/row2" app:layout_constraintTop_toTopOf="@id/line_2" /> <TextView android:id="@+id/cell_34" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[17]}" app:layout_constraintBottom_toBottomOf="@id/line_3" app:layout_constraintEnd_toEndOf="@id/row4" app:layout_constraintStart_toEndOf="@+id/row3" app:layout_constraintTop_toTopOf="@id/line_2" /> <TextView android:id="@+id/cell_35" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[18]}" app:layout_constraintBottom_toBottomOf="@id/line_3" app:layout_constraintEnd_toEndOf="@id/row5" app:layout_constraintStart_toEndOf="@+id/row4" app:layout_constraintTop_toTopOf="@id/line_2" /> <TextView android:id="@+id/cell_36" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[19]}" app:layout_constraintBottom_toBottomOf="@id/line_3" app:layout_constraintEnd_toEndOf="@id/row6" app:layout_constraintStart_toEndOf="@+id/row5" app:layout_constraintTop_toTopOf="@id/line_2" /> <TextView android:id="@+id/cell_37" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_blue" android:gravity="center_horizontal|center_vertical" android:text="@{date[20]}" app:layout_constraintBottom_toBottomOf="@id/line_3" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/row6" app:layout_constraintTop_toTopOf="@id/line_2" /> <TextView android:id="@+id/cell_41" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_red" android:gravity="center_horizontal|center_vertical" android:text="@{date[21]}" app:layout_constraintBottom_toBottomOf="@id/line_4" app:layout_constraintEnd_toEndOf="@id/row1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/line_3" /> <TextView android:id="@+id/cell_42" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[22]}" app:layout_constraintBottom_toBottomOf="@id/line_4" app:layout_constraintEnd_toEndOf="@id/row2" app:layout_constraintStart_toEndOf="@+id/row1" app:layout_constraintTop_toTopOf="@id/line_3" /> <TextView android:id="@+id/cell_43" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[23]}" app:layout_constraintBottom_toBottomOf="@id/line_4" app:layout_constraintEnd_toEndOf="@id/row3" app:layout_constraintStart_toEndOf="@+id/row2" app:layout_constraintTop_toTopOf="@id/line_3" /> <TextView android:id="@+id/cell_44" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[24]}" app:layout_constraintBottom_toBottomOf="@id/line_4" app:layout_constraintEnd_toEndOf="@id/row4" app:layout_constraintStart_toEndOf="@+id/row3" app:layout_constraintTop_toTopOf="@id/line_3" /> <TextView android:id="@+id/cell_45" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[25]}" app:layout_constraintBottom_toBottomOf="@id/line_4" app:layout_constraintEnd_toEndOf="@id/row5" app:layout_constraintStart_toEndOf="@+id/row4" app:layout_constraintTop_toTopOf="@id/line_3" /> <TextView android:id="@+id/cell_46" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[26]}" app:layout_constraintBottom_toBottomOf="@id/line_4" app:layout_constraintEnd_toEndOf="@id/row6" app:layout_constraintStart_toEndOf="@+id/row5" app:layout_constraintTop_toTopOf="@id/line_3" /> <TextView android:id="@+id/cell_47" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_blue" android:gravity="center_horizontal|center_vertical" android:text="@{date[27]}" app:layout_constraintBottom_toBottomOf="@id/line_4" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/row6" app:layout_constraintTop_toTopOf="@id/line_3" /> <TextView android:id="@+id/cell_51" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_red" android:gravity="center_horizontal|center_vertical" android:text="@{date[28]}" app:layout_constraintBottom_toBottomOf="@id/line_5" app:layout_constraintEnd_toEndOf="@id/row1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/line_4" /> <TextView android:id="@+id/cell_52" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[29]}" app:layout_constraintBottom_toBottomOf="@id/line_5" app:layout_constraintEnd_toEndOf="@id/row2" app:layout_constraintStart_toEndOf="@+id/row1" app:layout_constraintTop_toTopOf="@id/line_4" /> <TextView android:id="@+id/cell_53" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[30]}" app:layout_constraintBottom_toBottomOf="@id/line_5" app:layout_constraintEnd_toEndOf="@id/row3" app:layout_constraintStart_toEndOf="@+id/row2" app:layout_constraintTop_toTopOf="@id/line_4" /> <TextView android:id="@+id/cell_54" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[31]}" app:layout_constraintBottom_toBottomOf="@id/line_5" app:layout_constraintEnd_toEndOf="@id/row4" app:layout_constraintStart_toEndOf="@+id/row3" app:layout_constraintTop_toTopOf="@id/line_4" /> <TextView android:id="@+id/cell_55" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[32]}" app:layout_constraintBottom_toBottomOf="@id/line_5" app:layout_constraintEnd_toEndOf="@id/row5" app:layout_constraintStart_toEndOf="@+id/row4" app:layout_constraintTop_toTopOf="@id/line_4" /> <TextView android:id="@+id/cell_56" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[33]}" app:layout_constraintBottom_toBottomOf="@id/line_5" app:layout_constraintEnd_toEndOf="@id/row6" app:layout_constraintStart_toEndOf="@+id/row5" app:layout_constraintTop_toTopOf="@id/line_4" /> <TextView android:id="@+id/cell_57" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_blue" android:gravity="center_horizontal|center_vertical" android:text="@{date[34]}" app:layout_constraintBottom_toBottomOf="@id/line_5" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/row6" app:layout_constraintTop_toTopOf="@id/line_4" /> <TextView android:id="@+id/cell_61" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_red" android:gravity="center_horizontal|center_vertical" android:text="@{date[35]}" app:layout_constraintBottom_toBottomOf="@id/line_6" app:layout_constraintEnd_toEndOf="@id/row1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/line_5" /> <TextView android:id="@+id/cell_62" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[36]}" app:layout_constraintBottom_toBottomOf="@id/line_6" app:layout_constraintEnd_toEndOf="@id/row2" app:layout_constraintStart_toEndOf="@+id/row1" app:layout_constraintTop_toTopOf="@id/line_5" /> <TextView android:id="@+id/cell_63" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[37]}" app:layout_constraintBottom_toBottomOf="@id/line_6" app:layout_constraintEnd_toEndOf="@id/row3" app:layout_constraintStart_toEndOf="@+id/row2" app:layout_constraintTop_toTopOf="@id/line_5" /> <TextView android:id="@+id/cell_64" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[38]}" app:layout_constraintBottom_toBottomOf="@id/line_6" app:layout_constraintEnd_toEndOf="@id/row4" app:layout_constraintStart_toEndOf="@+id/row3" app:layout_constraintTop_toTopOf="@id/line_5" /> <TextView android:id="@+id/cell_65" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[39]}" app:layout_constraintBottom_toBottomOf="@id/line_6" app:layout_constraintEnd_toEndOf="@id/row5" app:layout_constraintStart_toEndOf="@+id/row4" app:layout_constraintTop_toTopOf="@id/line_5" /> <TextView android:id="@+id/cell_66" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style" android:gravity="center_horizontal|center_vertical" android:text="@{date[40]}" app:layout_constraintBottom_toBottomOf="@id/line_6" app:layout_constraintEnd_toEndOf="@id/row6" app:layout_constraintStart_toEndOf="@+id/row5" app:layout_constraintTop_toTopOf="@id/line_5" /> <TextView android:id="@+id/cell_67" android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/flame_style_blue" android:gravity="center_horizontal|center_vertical" android:text="@{date[41]}" app:layout_constraintBottom_toBottomOf="@id/line_6" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/row6" app:layout_constraintTop_toTopOf="@id/line_5" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>CalendarFragment.ktpackage com.example.myfavoritecontentsmanage import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import com.example.myfavoritecontentsmanage.databinding.FragmentCalendarBinding import java.util.* class CalendarFragment() : Fragment() { private lateinit var binding: FragmentCalendarBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = FragmentCalendarBinding.inflate(inflater, container, false) inflater.inflate(R.layout.fragment_calendar, container, false) val calendar = Calendar.getInstance() val year = 2019 val month = 6 val minDay = 1 calendar.set(year, month - 1, 1) val maxDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH) val blankDays = calendar.get(Calendar.DAY_OF_WEEK) val dayArray: MutableList<String> = mutableListOf() for (i in minDay until blankDays) { dayArray.add("") } for (i in minDay..maxDay) { dayArray.add(i.toString()) } binding.date = dayArray.toTypedArray() return binding.root } }基本的にただ移行するだけなのでそこまで苦戦することはありません。
ViewPager2の利用
前提
ViewPager2を使うためにはgradleに下記の記述を追加します。implementation "androidx.viewpager2:viewpager2:1.0.0"まず、アクティビティのレイアウトファイルでviewPagerを定義します。
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>そしてMainActivityでadapterを呼び出し、連結します。
MainActivity.ktpackage com.example.myfavoritecontentsmanage import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter import com.example.myfavoritecontentsmanage.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { binding = DataBindingUtil.setContentView(this, R.layout.activity_main) super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main) val pager = ScreenSlidePagerAdapter(this) binding.pager.adapter = pager } private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { override fun getItemCount(): Int = 3 override fun createFragment(position: Int): Fragment = CalendarFragment(position) } }実行結果がこちら!
ちょっと月の表示が無いのでわかりにくいかもしれないのですが、ちゃんと切り替わっている様子が伝わるかと!!!
今回は3の制限が付いていて、しかも5月など過去の月が表示できないのでその辺りを調整しようと思います!