实现fab随RecyclerView滑动隐藏显示,增加添加联系人页面,实现联系人拖动
This commit is contained in:
@@ -25,6 +25,18 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.contact.add.ContactAddActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".activity.contact.edit.ContactEditActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".activity.contact.list.ContactListActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
|
||||
<provider
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.ttstd.dialer.activity.contact.add;
|
||||
|
||||
import android.text.InputFilter;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
|
||||
import com.ttstd.dialer.contact.Contact;
|
||||
import com.ttstd.dialer.databinding.ActivityContactAddBinding;
|
||||
import com.ttstd.dialer.filter.NoSpaceInputFilter;
|
||||
|
||||
public class ContactAddActivity extends BaseMvvmActivity<ContactAddViewModel, ActivityContactAddBinding> {
|
||||
|
||||
@Override
|
||||
public boolean setNightMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setfitWindow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.activity_contact_add;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataBinding() {
|
||||
mViewModel.setContext(this);
|
||||
mViewModel.setVDBinding(mViewDataBinding);
|
||||
mViewModel.setLifecycle(getLifecycleSubject());
|
||||
mViewDataBinding.setClick(new BtnClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
mViewDataBinding.etName.setFilters(new InputFilter[]{new NoSpaceInputFilter()});
|
||||
mViewDataBinding.etPhone.setFilters(new InputFilter[]{new NoSpaceInputFilter()});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initData() {
|
||||
mViewModel.mIntegerMutableLiveData.observe(this, new Observer<Long>() {
|
||||
@Override
|
||||
public void onChanged(Long aLong) {
|
||||
if (aLong > 0) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public class BtnClick {
|
||||
public void save(View view) {
|
||||
String name = mViewDataBinding.etName.getText().toString();
|
||||
String phone = mViewDataBinding.etPhone.getText().toString();
|
||||
Contact contact = new Contact(name, phone);
|
||||
mViewModel.insert(contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ttstd.dialer.activity.contact.add;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
||||
import com.ttstd.dialer.contact.Contact;
|
||||
import com.ttstd.dialer.contact.ContactRepository;
|
||||
import com.ttstd.dialer.databinding.ActivityContactAddBinding;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ContactAddViewModel extends BaseViewModel<ActivityContactAddBinding, ActivityEvent> {
|
||||
private static final String TAG = "ContactAddViewModel";
|
||||
private ContactRepository mRepository;
|
||||
|
||||
@Override
|
||||
public void setContext(Context context) {
|
||||
super.setContext(context);
|
||||
mRepository = new ContactRepository(context);
|
||||
}
|
||||
|
||||
public MutableLiveData<Long> mIntegerMutableLiveData = new MutableLiveData<>();
|
||||
|
||||
public void insert(Contact contact) {
|
||||
Log.e(TAG, "insert: " );
|
||||
mIntegerMutableLiveData.setValue(mRepository.insert(contact));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.ttstd.dialer.activity.contact.edit;
|
||||
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
|
||||
import com.ttstd.dialer.databinding.ActivityContactEditBinding;
|
||||
|
||||
public class ContactEditActivity extends BaseMvvmActivity<ContactEditViewModel, ActivityContactEditBinding> {
|
||||
|
||||
@Override
|
||||
public boolean setNightMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.activity_contact_edit;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataBinding() {
|
||||
mViewModel.setContext(this);
|
||||
mViewModel.setVDBinding(mViewDataBinding);
|
||||
mViewModel.setLifecycle(getLifecycleSubject());
|
||||
mViewDataBinding.setClick(new BtnClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initData() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class BtnClick{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ttstd.dialer.activity.contact.edit;
|
||||
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
||||
import com.ttstd.dialer.databinding.ActivityContactEditBinding;
|
||||
|
||||
public class ContactEditViewModel extends BaseViewModel <ActivityContactEditBinding, ActivityEvent>{
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.ttstd.dialer.activity.contact.list;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.activity.contact.add.ContactAddActivity;
|
||||
import com.ttstd.dialer.adapter.ContactInfoAdapter;
|
||||
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
|
||||
import com.ttstd.dialer.contact.Contact;
|
||||
import com.ttstd.dialer.databinding.ActivityContactListBinding;
|
||||
import com.ttstd.dialer.view.ItemTouchHelperCallback;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ContactListActivity extends BaseMvvmActivity<ContactListViewModel, ActivityContactListBinding> {
|
||||
|
||||
private static final String TAG = "ContactListActivity";
|
||||
|
||||
private ContactInfoAdapter mContactInfoAdapter;
|
||||
|
||||
@Override
|
||||
public boolean setNightMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setfitWindow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.activity_contact_list;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataBinding() {
|
||||
mViewModel.setContext(this);
|
||||
mViewModel.setVDBinding(mViewDataBinding);
|
||||
mViewModel.setLifecycle(getLifecycleSubject());
|
||||
mViewDataBinding.setClick(new BtnClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
mContactInfoAdapter = new ContactInfoAdapter();
|
||||
mContactInfoAdapter.setItemMoveCallback(new ContactInfoAdapter.ItemMoveCallback() {
|
||||
@Override
|
||||
public void onItemMove(int fromPosition, int toPosition) {
|
||||
Log.e(TAG, "onItemMove: " );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRemoved(int position) {
|
||||
Log.e(TAG, "onItemRemoved: " );
|
||||
}
|
||||
});
|
||||
// 设置ItemTouchHelper,实现拖动和滑动删除
|
||||
ItemTouchHelper.Callback callback = new ItemTouchHelperCallback(mContactInfoAdapter);
|
||||
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
|
||||
touchHelper.attachToRecyclerView(mViewDataBinding.recyclerView);
|
||||
|
||||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
|
||||
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
|
||||
mViewDataBinding.recyclerView.setLayoutManager(linearLayoutManager);
|
||||
mViewDataBinding.recyclerView.setAdapter(mContactInfoAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initData() {
|
||||
mViewModel.mContactListData.observe(this, new Observer<List<Contact>>() {
|
||||
@Override
|
||||
public void onChanged(List<Contact> contacts) {
|
||||
Log.e(TAG, "onChanged: " + contacts);
|
||||
mContactInfoAdapter.setContacts(contacts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mViewModel.getAllContacts();
|
||||
}
|
||||
|
||||
public class BtnClick {
|
||||
public void addContact(View view) {
|
||||
startActivity(new Intent(ContactListActivity.this, ContactAddActivity.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.ttstd.dialer.activity.contact.list;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
||||
import com.ttstd.dialer.contact.Contact;
|
||||
import com.ttstd.dialer.contact.ContactRepository;
|
||||
import com.ttstd.dialer.databinding.ActivityContactListBinding;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ContactListViewModel extends BaseViewModel<ActivityContactListBinding, ActivityEvent> {
|
||||
private static final String TAG = "ContactListViewModel";
|
||||
private ContactRepository mRepository;
|
||||
|
||||
@Override
|
||||
public void setContext(Context context) {
|
||||
super.setContext(context);
|
||||
mRepository = new ContactRepository(context);
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Contact>> mContactListData = new MutableLiveData<>();
|
||||
|
||||
public void getAllContacts() {
|
||||
List<Contact> contacts = mRepository.getAllContacts();
|
||||
List<Contact> sorted = contacts.stream().sorted(new Comparator<Contact>() {
|
||||
@Override
|
||||
public int compare(Contact o1, Contact o2) {
|
||||
return Integer.compare(o1.getSort(), o2.getSort());
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
mContactListData.setValue(sorted);
|
||||
}
|
||||
|
||||
public List<Contact> searchContacts(String query) {
|
||||
return mRepository.searchContacts(query);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.ttstd.dialer.adapter;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.shehuan.niv.NiceImageView;
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.contact.Contact;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ContactInfoAdapter extends RecyclerView.Adapter<ContactInfoAdapter.ContactInfoHolder> {
|
||||
private static final String TAG = "ContactInfoAdapter";
|
||||
|
||||
private FragmentActivity mContext;
|
||||
|
||||
private List<Contact> mContacts;
|
||||
|
||||
public void setContacts(List<Contact> contacts) {
|
||||
mContacts = contacts;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setItemMoveCallback(ItemMoveCallback itemMoveCallback) {
|
||||
mItemMoveCallback = itemMoveCallback;
|
||||
}
|
||||
|
||||
private ItemMoveCallback mItemMoveCallback;
|
||||
|
||||
public interface ItemMoveCallback {
|
||||
void onItemMove(int fromPosition, int toPosition);
|
||||
|
||||
void onItemRemoved(int position);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ContactInfoHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
mContext = (FragmentActivity) parent.getContext();
|
||||
return new ContactInfoHolder(LayoutInflater.from(mContext).inflate(R.layout.item_contact, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ContactInfoHolder holder, int position) {
|
||||
Contact contact = mContacts.get(position);
|
||||
String name = contact.getName();
|
||||
holder.tv_name.setText(name);
|
||||
String phone = contact.getPhoneNumber();
|
||||
holder.tv_phone.setText(phone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mContacts == null ? 0 : mContacts.size();
|
||||
}
|
||||
|
||||
|
||||
// 处理拖动交换位置
|
||||
public void onItemMove(int fromPosition, int toPosition) {
|
||||
Log.e(TAG, "onItemMove: fromPosition = " + fromPosition);
|
||||
Log.e(TAG, "onItemMove: toPosition = " + toPosition);
|
||||
if (fromPosition == toPosition) {
|
||||
return; // 直接返回,不执行后续操作
|
||||
}
|
||||
|
||||
if (fromPosition < toPosition) {
|
||||
for (int i = fromPosition; i < toPosition; i++) {
|
||||
Collections.swap(mContacts, i, i + 1);
|
||||
}
|
||||
} else {
|
||||
for (int i = fromPosition; i > toPosition; i--) {
|
||||
Collections.swap(mContacts, i, i - 1);
|
||||
}
|
||||
}
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
// 处理滑动删除
|
||||
public void onItemDismiss(int position) {
|
||||
// mContacts.remove(position);
|
||||
if (mItemMoveCallback != null) {
|
||||
mItemMoveCallback.onItemRemoved(position);
|
||||
}
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
public class ContactInfoHolder extends RecyclerView.ViewHolder {
|
||||
NiceImageView nv_avatar;
|
||||
TextView tv_name, tv_phone;
|
||||
|
||||
public ContactInfoHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
nv_avatar = itemView.findViewById(R.id.nv_avatar);
|
||||
tv_name = itemView.findViewById(R.id.tv_name);
|
||||
tv_phone = itemView.findViewById(R.id.tv_phone);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import androidx.room.Room;
|
||||
import androidx.room.RoomDatabase;
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Database(entities = {Contact.class}, version = 1, exportSchema = false)
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract ContactDao contactDao();
|
||||
@@ -17,7 +19,7 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||
synchronized (AppDatabase.class) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
|
||||
AppDatabase.class, "contact_database")
|
||||
AppDatabase.class, context.getExternalCacheDir() + File.separator +"contact_database")
|
||||
.allowMainThreadQueries() // 为了简化示例,允许主线程查询
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -19,10 +19,12 @@ public class Contact implements Serializable {
|
||||
private String name;
|
||||
private String phoneNumber;
|
||||
private String avatar;
|
||||
private int sort;
|
||||
|
||||
public Contact(String name, String phoneNumber) {
|
||||
this.name = name;
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.sort = 0;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
@@ -57,6 +59,14 @@ public class Contact implements Serializable {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public int getSort() {
|
||||
return sort;
|
||||
}
|
||||
|
||||
public void setSort(int sort) {
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj instanceof Contact) {
|
||||
|
||||
@@ -11,19 +11,19 @@ import java.util.List;
|
||||
@Dao
|
||||
public interface ContactDao {
|
||||
@Insert
|
||||
void insert(Contact contact);
|
||||
long insert(Contact contact);
|
||||
|
||||
@Update
|
||||
void update(Contact contact);
|
||||
int update(Contact contact);
|
||||
|
||||
@Delete
|
||||
void delete(Contact contact);
|
||||
int delete(Contact contact);
|
||||
|
||||
@Query("DELETE FROM contacts WHERE id = :id")
|
||||
void deleteById(int id);
|
||||
int deleteById(int id);
|
||||
|
||||
@Query("DELETE FROM contacts")
|
||||
void deleteAll();
|
||||
int deleteAll();
|
||||
|
||||
@Query("SELECT * FROM contacts ORDER BY name ASC")
|
||||
List<Contact> getAllContacts();
|
||||
|
||||
@@ -30,27 +30,27 @@ public class ContactRepository {
|
||||
}
|
||||
|
||||
// 添加联系人
|
||||
public void insert(Contact contact) {
|
||||
mContactDao.insert(contact);
|
||||
public long insert(Contact contact) {
|
||||
return mContactDao.insert(contact);
|
||||
}
|
||||
|
||||
// 更新联系人
|
||||
public void update(Contact contact) {
|
||||
mContactDao.update(contact);
|
||||
public int update(Contact contact) {
|
||||
return mContactDao.update(contact);
|
||||
}
|
||||
|
||||
// 删除联系人
|
||||
public void delete(Contact contact) {
|
||||
mContactDao.delete(contact);
|
||||
public int delete(Contact contact) {
|
||||
return mContactDao.delete(contact);
|
||||
}
|
||||
|
||||
// 根据ID删除联系人
|
||||
public void deleteById(int id) {
|
||||
mContactDao.deleteById(id);
|
||||
public int deleteById(int id) {
|
||||
return mContactDao.deleteById(id);
|
||||
}
|
||||
|
||||
// 删除所有联系人
|
||||
public void deleteAll() {
|
||||
mContactDao.deleteAll();
|
||||
public int deleteAll() {
|
||||
return mContactDao.deleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ttstd.dialer.filter;
|
||||
|
||||
import android.text.InputFilter;
|
||||
import android.text.Spanned;
|
||||
|
||||
/**
|
||||
* 过滤输入中的空格(包括普通空格和全角空格)
|
||||
*/
|
||||
public class NoSpaceInputFilter implements InputFilter {
|
||||
@Override
|
||||
public CharSequence filter(CharSequence source, int start, int end,
|
||||
Spanned dest, int dstart, int dend) {
|
||||
// 遍历输入的字符序列,过滤掉所有空格
|
||||
StringBuilder filtered = new StringBuilder();
|
||||
for (int i = start; i < end; i++) {
|
||||
char c = source.charAt(i);
|
||||
// 过滤普通空格(ASCII 32)和全角空格(Unicode 12288)
|
||||
if (c != ' ' && c != '\u3000') {
|
||||
filtered.append(c);
|
||||
}
|
||||
}
|
||||
// 如果过滤后的结果与原输入不同,返回过滤后的内容;否则返回原内容(允许输入)
|
||||
return filtered.length() == (end - start) ? null : filtered;
|
||||
}
|
||||
}
|
||||
@@ -8,24 +8,20 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.hjq.toast.Toaster;
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.base.BaseFragment;
|
||||
import com.ttstd.dialer.activity.contact.list.ContactListActivity;
|
||||
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
|
||||
import com.ttstd.dialer.databinding.FragmentHomeBinding;
|
||||
import com.ttstd.dialer.utils.ApkUtils;
|
||||
import com.ttstd.dialer.utils.DataUtil;
|
||||
import com.ttstd.dialer.utils.LunarCalendarFestivalUtils;
|
||||
import com.ttstd.dialer.utils.TimeUtils;
|
||||
|
||||
/**
|
||||
* A simple {@link Fragment} subclass.
|
||||
@@ -190,7 +186,7 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
|
||||
|
||||
public class BtnClick {
|
||||
public void openContact(View view) {
|
||||
Intent intent = new Intent();
|
||||
Intent intent = new Intent(mContext, ContactListActivity.class);
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
|
||||
117
app/src/main/java/com/ttstd/dialer/view/FabBehavior.java
Normal file
117
app/src/main/java/com/ttstd/dialer/view/FabBehavior.java
Normal file
@@ -0,0 +1,117 @@
|
||||
package com.ttstd.dialer.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.ViewPropertyAnimatorListener;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.ttstd.dialer.R;
|
||||
|
||||
public class FabBehavior extends Behavior<FloatingActionButton> {
|
||||
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
|
||||
private boolean isAnimatingOut = false;
|
||||
private boolean visible = true;
|
||||
|
||||
public FabBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
|
||||
FloatingActionButton child,
|
||||
View directTargetChild,
|
||||
View target,
|
||||
int axes,
|
||||
int type) {
|
||||
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNestedScroll(CoordinatorLayout coordinatorLayout,
|
||||
FloatingActionButton child,
|
||||
View target,
|
||||
int dxConsumed,
|
||||
int dyConsumed,
|
||||
int dxUnconsumed,
|
||||
int dyUnconsumed,
|
||||
int type) {
|
||||
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
|
||||
dxUnconsumed, dyUnconsumed, type);
|
||||
|
||||
if (dyConsumed > 0 && !isAnimatingOut && child.getVisibility() == View.VISIBLE) {
|
||||
// 向下滑动,隐藏FAB
|
||||
animateOut(child);
|
||||
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
|
||||
// 向上滑动,显示FAB
|
||||
animateIn(child);
|
||||
}
|
||||
}
|
||||
|
||||
private void animateOut(final FloatingActionButton button) {
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
ViewCompat.animate(button)
|
||||
.scaleX(0.0f)
|
||||
.scaleY(0.0f)
|
||||
.alpha(0.0f)
|
||||
.setInterpolator(INTERPOLATOR)
|
||||
.withLayer()
|
||||
.setListener(new ViewPropertyAnimatorListener() {
|
||||
public void onAnimationStart(View view) {
|
||||
isAnimatingOut = true;
|
||||
}
|
||||
public void onAnimationCancel(View view) {
|
||||
isAnimatingOut = false;
|
||||
}
|
||||
public void onAnimationEnd(View view) {
|
||||
isAnimatingOut = false;
|
||||
view.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_out);
|
||||
anim.setInterpolator(INTERPOLATOR);
|
||||
anim.setDuration(200L);
|
||||
anim.setAnimationListener(new Animation.AnimationListener() {
|
||||
public void onAnimationStart(Animation animation) {
|
||||
isAnimatingOut = true;
|
||||
}
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
isAnimatingOut = false;
|
||||
button.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationRepeat(final Animation animation) {}
|
||||
});
|
||||
button.startAnimation(anim);
|
||||
}
|
||||
}
|
||||
|
||||
private void animateIn(FloatingActionButton button) {
|
||||
button.setVisibility(View.VISIBLE);
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
ViewCompat.animate(button)
|
||||
.scaleX(1.0f)
|
||||
.scaleY(1.0f)
|
||||
.alpha(1.0f)
|
||||
.setInterpolator(INTERPOLATOR)
|
||||
.withLayer()
|
||||
.setListener(null)
|
||||
.start();
|
||||
} else {
|
||||
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_in);
|
||||
anim.setDuration(200L);
|
||||
anim.setInterpolator(INTERPOLATOR);
|
||||
button.startAnimation(anim);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.ttstd.dialer.view;
|
||||
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.ttstd.dialer.adapter.ContactInfoAdapter;
|
||||
|
||||
|
||||
// 处理拖动和滑动事件
|
||||
public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
private final ContactInfoAdapter mAdapter;
|
||||
|
||||
public ItemTouchHelperCallback(ContactInfoAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
}
|
||||
|
||||
// 定义支持的拖动方向
|
||||
@Override
|
||||
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||
// 允许上下拖动
|
||||
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN ;
|
||||
// 允许左右滑动删除
|
||||
int swipeFlags = ItemTouchHelper.START;
|
||||
return makeMovementFlags(dragFlags, swipeFlags);
|
||||
}
|
||||
|
||||
// 处理拖动交换位置
|
||||
@Override
|
||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
|
||||
RecyclerView.ViewHolder target) {
|
||||
// 通知适配器项已移动
|
||||
mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理滑动删除
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
// 通知适配器项已删除
|
||||
mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
|
||||
}
|
||||
|
||||
// 当长按item时启用拖动
|
||||
@Override
|
||||
public boolean isLongPressDragEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 启用滑动删除
|
||||
@Override
|
||||
public boolean isItemViewSwipeEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.ttstd.dialer.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.ViewPropertyAnimatorListener;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
|
||||
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
|
||||
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
|
||||
private boolean mIsAnimatingOut = false;
|
||||
|
||||
public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
|
||||
final View directTargetChild, final View target, final int nestedScrollAxes) {
|
||||
// Ensure we react to vertical scrolling
|
||||
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
|
||||
|| super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
|
||||
final View target, final int dxConsumed, final int dyConsumed,
|
||||
final int dxUnconsumed, final int dyUnconsumed) {
|
||||
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
|
||||
if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
|
||||
// User scrolled down and the FAB is currently visible -> hide the FAB
|
||||
animateOut(child);
|
||||
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
|
||||
// User scrolled up and the FAB is currently not visible -> show the FAB
|
||||
animateIn(child);
|
||||
}
|
||||
}
|
||||
|
||||
private void animateOut(final FloatingActionButton button) {
|
||||
ViewCompat.animate(button).translationY(button.getHeight() + getMarginBottom(button)).setInterpolator(INTERPOLATOR).withLayer()
|
||||
.setListener(new ViewPropertyAnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
|
||||
view.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void animateIn(FloatingActionButton button) {
|
||||
button.setVisibility(View.VISIBLE);
|
||||
ViewCompat.animate(button).translationY(0)
|
||||
.setInterpolator(INTERPOLATOR).withLayer().setListener(null)
|
||||
.start();
|
||||
}
|
||||
|
||||
private int getMarginBottom(View v) {
|
||||
int marginBottom = 0;
|
||||
final ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
|
||||
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
||||
marginBottom = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin;
|
||||
}
|
||||
return marginBottom;
|
||||
}
|
||||
}
|
||||
5
app/src/main/res/anim/fab_in.xml
Normal file
5
app/src/main/res/anim/fab_in.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="300"
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0" />
|
||||
5
app/src/main/res/anim/fab_out.xml
Normal file
5
app/src/main/res/anim/fab_out.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="300"
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0" />
|
||||
12
app/src/main/res/drawable/ic_default_avatar.xml
Normal file
12
app/src/main/res/drawable/ic_default_avatar.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M0,0m91.02,0l841.96,0q91.02,0 91.02,91.02l0,841.96q0,91.02 -91.02,91.02l-841.96,0q-91.02,0 -91.02,-91.02l0,-841.96q0,-91.02 91.02,-91.02Z"
|
||||
android:fillColor="#C5C5C5"/>
|
||||
<path
|
||||
android:pathData="M794.83,824.43c-60.46,-27.12 -37.16,-6.35 -111.37,-34.16 -74.18,-27.67 -91.75,-36.75 -91.75,-36.75l4.3,-48.51s68.12,-21.17 80.9,-104.25c25.62,6.28 34.29,-25.28 35.66,-45.51 1.58,-19.54 15.18,-80.33 -16.19,-74.93 6.43,-40.58 11.49,-77.28 9.16,-96.75 -7.79,-68.19 -63.67,-139.38 -204.62,-144.63 -31.79,1.17 -63.15,7.76 -92.71,19.47 -10.39,4.1 -77.89,-4.3 -86.97,14.14 -4.93,10.05 10.24,40.52 2.05,47.08 -33,26.51 -24.12,31.22 -27.88,64.01 -2.25,19.47 2.32,56.09 8.74,96.81 -31.29,-5.46 -17.75,55.33 -16.38,74.87 1.42,20.23 9.9,51.93 35.59,45.65C336.13,684.04 419.41,705.42 419.41,705.42l4.31,48.72s-17.57,9.69 -91.76,37.43c-74.18,27.74 -50.89,5.73 -111.35,32.79C125.16,867.41 125.16,985.33 125.16,985.33c0,17.91 16.05,32.46 35.86,32.46H854.47c19.82,0 35.87,-14.56 35.87,-32.39v-0.07s0,-117.92 -95.52,-160.82v-0.07z"
|
||||
android:fillColor="#E5E5E5"/>
|
||||
</vector>
|
||||
114
app/src/main/res/layout/activity_contact_add.xml
Normal file
114
app/src/main/res/layout/activity_contact_add.xml
Normal file
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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"
|
||||
tools:context=".activity.contact.add.ContactAddActivity">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="click"
|
||||
type="com.ttstd.dialer.activity.contact.add.ContactAddActivity.BtnClick" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toTopOf="@+id/tv_save"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:text="姓名"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="姓名"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/tv_name"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_phone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:text="电话"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_phone"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="电话"
|
||||
android:inputType="phone"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/tv_phone"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_save"
|
||||
android:layout_width="wrap_content"
|
||||
android:onClick="@{click::save}"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:text="保存"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
17
app/src/main/res/layout/activity_contact_edit.xml
Normal file
17
app/src/main/res/layout/activity_contact_edit.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".activity.contact.edit.ContactEditActivity">
|
||||
|
||||
<data>
|
||||
<variable
|
||||
name="click"
|
||||
type="com.ttstd.dialer.activity.contact.edit.ContactEditActivity.BtnClick" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
37
app/src/main/res/layout/activity_contact_list.xml
Normal file
37
app/src/main/res/layout/activity_contact_list.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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"
|
||||
tools:context=".activity.contact.list.ContactListActivity">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="click"
|
||||
type="com.ttstd.dialer.activity.contact.list.ContactListActivity.BtnClick" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:onClick="@{click::addContact}"
|
||||
android:src="@drawable/ic_add"
|
||||
app:fabCustomSize="64dp"
|
||||
app:layout_behavior="com.ttstd.dialer.view.ScrollAwareFABBehavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
||||
65
app/src/main/res/layout/item_contact.xml
Normal file
65
app/src/main/res/layout/item_contact.xml
Normal file
@@ -0,0 +1,65 @@
|
||||
<?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.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.shehuan.niv.NiceImageView
|
||||
android:id="@+id/nv_avatar"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_default_avatar"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/nv_avatar"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:text="姓名"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_phone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:text="姓名"
|
||||
android:textColor="@color/gray"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -3,4 +3,5 @@
|
||||
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
<string name="floating_action_button_behavior">com.ttstd.dialer.view.ScrollAwareFABBehavior</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user