From 658ea49ac48b5bb88c1939a186151648c3bfe8d0 Mon Sep 17 00:00:00 2001 From: niushuai233 Date: Thu, 11 Apr 2024 17:44:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20:six:=20icon=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cc/niushuai/dididone/MainActivity.java | 42 ++++ .../dididone/ui/base/InitAndSetListener.java | 14 ++ .../ui/setting/icon/IconFragment.java | 53 ++++- .../icon/recycle/IconRecycleAdapter.java | 91 ++++++++ .../cc/niushuai/dididone/util/Snowflake.java | 199 ++++++++++++++++++ app/src/main/res/layout/fragment_icon.xml | 97 ++------- app/src/main/res/layout/icon_grid_item.xml | 23 ++ 7 files changed, 433 insertions(+), 86 deletions(-) create mode 100644 app/src/main/java/cc/niushuai/dididone/ui/base/InitAndSetListener.java create mode 100644 app/src/main/java/cc/niushuai/dididone/ui/setting/icon/recycle/IconRecycleAdapter.java create mode 100644 app/src/main/java/cc/niushuai/dididone/util/Snowflake.java create mode 100644 app/src/main/res/layout/icon_grid_item.xml diff --git a/app/src/main/java/cc/niushuai/dididone/MainActivity.java b/app/src/main/java/cc/niushuai/dididone/MainActivity.java index 8731fec..1459b47 100644 --- a/app/src/main/java/cc/niushuai/dididone/MainActivity.java +++ b/app/src/main/java/cc/niushuai/dididone/MainActivity.java @@ -1,7 +1,10 @@ package cc.niushuai.dididone; +import android.graphics.Color; import android.os.Bundle; +import android.util.Log; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.view.LayoutInflaterCompat; import androidx.drawerlayout.widget.DrawerLayout; @@ -18,10 +21,19 @@ import com.mikepenz.foundation_icons_typeface_library.FoundationIcons; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.iconics.Iconics; import com.mikepenz.iconics.context.IconicsLayoutInflater2; +import com.mikepenz.iconics.typeface.IIcon; import com.mikepenz.ionicons_typeface_library.Ionicons; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import cc.niushuai.dididone.biz.entity.SavedIcon; import cc.niushuai.dididone.biz.roomx.DBManager; import cc.niushuai.dididone.databinding.ActivityMainBinding; +import cc.niushuai.dididone.ui.setting.icon.recycle.IconRecycleAdapter; +import cc.niushuai.dididone.util.Snowflake; public class MainActivity extends AppCompatActivity { @@ -37,8 +49,38 @@ public class MainActivity extends AppCompatActivity { Iconics.registerFont(new Ionicons()); Iconics.registerFont(new FontAwesome()); Iconics.registerFont(new FoundationIcons()); + + // 初始化icon map + addFontMap(DevIcon.Icon.class); + addFontMap(CommunityMaterial.Icon.class); + addFontMap(GoogleMaterial.Icon.class); + addFontMap(Ionicons.Icon.class); + addFontMap(FontAwesome.Icon.class); + addFontMap(FoundationIcons.Icon.class); } + private void addFontMap(Class iconClass) { + + if (!iconClass.isEnum()) { + Log.d(this.getClass().getSimpleName(), iconClass.getName() + " class not enum class"); + return; + } + + List items = new ArrayList<>(); + + for (IIcon enumConstant : iconClass.getEnumConstants()) { + SavedIcon icon = new SavedIcon(); + icon.setId(Snowflake.next_id()); + icon.setIcon(enumConstant.getName()); + icon.setColor(String.valueOf(Color.BLACK)); + icon.setSize(52); + items.add(icon); + } + + IconRecycleAdapter.ICON_MAP.put(IconRecycleAdapter.getIconClassName(iconClass), items); + } + + @Override protected void onCreate(Bundle savedInstanceState) { // 初始化icon列表 diff --git a/app/src/main/java/cc/niushuai/dididone/ui/base/InitAndSetListener.java b/app/src/main/java/cc/niushuai/dididone/ui/base/InitAndSetListener.java new file mode 100644 index 0000000..07d2b0f --- /dev/null +++ b/app/src/main/java/cc/niushuai/dididone/ui/base/InitAndSetListener.java @@ -0,0 +1,14 @@ +package cc.niushuai.dididone.ui.base; + +public interface InitAndSetListener { + + /** + * 初始化数据 + */ + void init(); + + /** + * 控件添加监听器 + */ + void setListeners(); +} diff --git a/app/src/main/java/cc/niushuai/dididone/ui/setting/icon/IconFragment.java b/app/src/main/java/cc/niushuai/dididone/ui/setting/icon/IconFragment.java index 935d6ca..617a6fd 100644 --- a/app/src/main/java/cc/niushuai/dididone/ui/setting/icon/IconFragment.java +++ b/app/src/main/java/cc/niushuai/dididone/ui/setting/icon/IconFragment.java @@ -8,18 +8,69 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.mikepenz.fontawesome_typeface_library.FontAwesome; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import cc.niushuai.dididone.biz.entity.SavedIcon; import cc.niushuai.dididone.databinding.FragmentIconBinding; +import cc.niushuai.dididone.ui.base.InitAndSetListener; +import cc.niushuai.dididone.ui.setting.icon.recycle.IconRecycleAdapter; -public class IconFragment extends Fragment { +public class IconFragment extends Fragment implements InitAndSetListener { private FragmentIconBinding binding; + private IconRecycleAdapter iconRecycleAdapter; + @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FragmentIconBinding.inflate(inflater, container, false); View root = binding.getRoot(); + init(); + setListeners(); + return root; } + + + /** + * 初始化数据 + */ + @Override + public void init() { + + initRecyclerView(); + } + + private void initRecyclerView() { + binding.iconsRecyclerView.addItemDecoration(new DividerItemDecoration(this.getActivity(), DividerItemDecoration.VERTICAL)); + DefaultItemAnimator itemAnimator = new DefaultItemAnimator(); + itemAnimator.setAddDuration(1000); + itemAnimator.setRemoveDuration(1000); + binding.iconsRecyclerView.setItemAnimator(itemAnimator); + + iconRecycleAdapter = new IconRecycleAdapter(getActivity(), FontAwesome.Icon.class); + binding.iconsRecyclerView.setAdapter(iconRecycleAdapter); + LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); + binding.iconsRecyclerView.setLayoutManager(layoutManager); + } + + /** + * 控件添加监听器 + */ + @Override + public void setListeners() { + + } } diff --git a/app/src/main/java/cc/niushuai/dididone/ui/setting/icon/recycle/IconRecycleAdapter.java b/app/src/main/java/cc/niushuai/dididone/ui/setting/icon/recycle/IconRecycleAdapter.java new file mode 100644 index 0000000..890243c --- /dev/null +++ b/app/src/main/java/cc/niushuai/dididone/ui/setting/icon/recycle/IconRecycleAdapter.java @@ -0,0 +1,91 @@ +package cc.niushuai.dididone.ui.setting.icon.recycle; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.mikepenz.iconics.IconicsDrawable; +import com.mikepenz.iconics.typeface.IIcon; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import cc.niushuai.dididone.MainActivity; +import cc.niushuai.dididone.R; +import cc.niushuai.dididone.biz.entity.SavedIcon; + +public class IconRecycleAdapter extends RecyclerView.Adapter { + + public static final Map> ICON_MAP = new HashMap<>(); + private List savedIconList; + + private Context context; + + public IconRecycleAdapter(Context context, Class iconClass) { + this.context = context; + this.savedIconList = ICON_MAP.get(getIconClassName(iconClass)); + } + + /** + * @param parent The ViewGroup into which the new View will be added after it is bound to + * an adapter position. + * @param viewType The view type of the new View. + * @return + */ + @NonNull + @Override + public IconRecycleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = View.inflate(context, R.layout.icon_grid_item, null); + return new IconRecycleViewHolder(view); + } + + /** + * @param holder The ViewHolder which should be updated to represent the contents of the + * item at the given position in the data set. + * @param position The position of the item within the adapter's data set. + */ + @Override + public void onBindViewHolder(@NonNull IconRecycleViewHolder holder, int position) { + SavedIcon icon = savedIconList.get(position); + holder.iconView.setImageDrawable(new IconicsDrawable(context) + .icon(icon.getIcon()) + .color(Integer.parseInt(icon.getColor())) + .sizeDp(icon.getSize())); + + holder.txtView.setText(icon.getIcon()); + } + + /** + * @return + */ + @Override + public int getItemCount() { + return savedIconList.size(); + } + + public static String getIconClassName(Class iconClass) { + String iconClassName = iconClass.getName(); + return iconClassName.substring(iconClassName.lastIndexOf(".") + 1, iconClassName.indexOf("$")); + + } + public static class IconRecycleViewHolder extends RecyclerView.ViewHolder { + + protected ImageView iconView; + protected TextView txtView; + + public IconRecycleViewHolder(@NonNull View itemView) { + super(itemView); + iconView = itemView.findViewById(R.id.icons_grid_item); + txtView = itemView.findViewById(R.id.icons_grid_txt); + } + + } + +} diff --git a/app/src/main/java/cc/niushuai/dididone/util/Snowflake.java b/app/src/main/java/cc/niushuai/dididone/util/Snowflake.java new file mode 100644 index 0000000..f884d0c --- /dev/null +++ b/app/src/main/java/cc/niushuai/dididone/util/Snowflake.java @@ -0,0 +1,199 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cc.niushuai.dididone.util; + +import java.net.NetworkInterface; +import java.util.Enumeration; +import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author funkye + * @author selfishlover + */ +public class Snowflake { + private static final Snowflake snowflake = new Snowflake(0L); + + public static long next_id() { + return snowflake.nextId(); + } + + public static String next_id_str() { + return snowflake.nextId() + ""; + } + + /** + * Start time cut (2020-05-03) + */ + private final long twepoch = 1588435200000L; + + /** + * The number of bits occupied by workerId + */ + private final int workerIdBits = 10; + + /** + * The number of bits occupied by timestamp + */ + private final int timestampBits = 41; + + /** + * The number of bits occupied by sequence + */ + private final int sequenceBits = 12; + + /** + * Maximum supported machine id, the result is 1023 + */ + private final int maxWorkerId = ~(-1 << workerIdBits); + /** + * mask that help to extract timestamp and sequence from a long + */ + private final long timestampAndSequenceMask = ~(-1L << (timestampBits + sequenceBits)); + /** + * business meaning: machine ID (0 ~ 1023) + * actual layout in memory: + * highest 1 bit: 0 + * middle 10 bit: workerId + * lowest 53 bit: all 0 + */ + private long workerId; + /** + * timestamp and sequence mix in one Long + * highest 11 bit: not used + * middle 41 bit: timestamp + * lowest 12 bit: sequence + */ + private AtomicLong timestampAndSequence; + + /** + * instantiate an IdWorker using given workerId + * + * @param workerId if null, then will auto assign one + */ + public Snowflake(Long workerId) { + initTimestampAndSequence(); + initWorkerId(workerId); + } + + /** + * init first timestamp and sequence immediately + */ + protected void initTimestampAndSequence() { + long timestamp = getNewestTimestamp(); + long timestampWithSequence = timestamp << sequenceBits; + this.timestampAndSequence = new AtomicLong(timestampWithSequence); + } + + /** + * init workerId + * + * @param workerId if null, then auto generate one + */ + protected void initWorkerId(Long workerId) { + if (workerId == null) { + workerId = generateWorkerId(); + } + if (workerId > maxWorkerId || workerId < 0) { + String message = String.format("worker Id can't be greater than %d or less than 0", maxWorkerId); + throw new IllegalArgumentException(message); + } + this.workerId = workerId << (timestampBits + sequenceBits); + } + + /** + * get next UUID(base on snowflake algorithm), which look like: + * highest 1 bit: always 0 + * next 10 bit: workerId + * next 41 bit: timestamp + * lowest 12 bit: sequence + * + * @return UUID + */ + public long nextId() { + waitIfNecessary(); + long next = timestampAndSequence.incrementAndGet(); + long timestampWithSequence = next & timestampAndSequenceMask; + return workerId | timestampWithSequence; + } + + /** + * block current thread if the QPS of acquiring UUID is too high + * that current sequence space is exhausted + */ + private void waitIfNecessary() { + long currentWithSequence = timestampAndSequence.get(); + long current = currentWithSequence >>> sequenceBits; + long newest = getNewestTimestamp(); + if (current >= newest) { + try { + Thread.sleep(5); + } catch (InterruptedException ignore) { + // don't care + } + } + } + + /** + * get newest timestamp relative to twepoch + */ + private long getNewestTimestamp() { + return System.currentTimeMillis() - twepoch; + } + + /** + * auto generate workerId, try using mac first, if failed, then randomly generate one + * + * @return workerId + */ + private long generateWorkerId() { + try { + return generateWorkerIdBaseOnMac(); + } catch (Exception e) { + return generateRandomWorkerId(); + } + } + + /** + * use lowest 10 bit of available MAC as workerId + * + * @return workerId + * @throws Exception when there is no available mac found + */ + private long generateWorkerIdBaseOnMac() throws Exception { + Enumeration all = NetworkInterface.getNetworkInterfaces(); + while (all.hasMoreElements()) { + NetworkInterface networkInterface = all.nextElement(); + boolean isLoopback = networkInterface.isLoopback(); + boolean isVirtual = networkInterface.isVirtual(); + if (isLoopback || isVirtual) { + continue; + } + byte[] mac = networkInterface.getHardwareAddress(); + return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF); + } + throw new RuntimeException("no available mac found"); + } + + /** + * randomly generate one as workerId + * + * @return workerId + */ + private long generateRandomWorkerId() { + return new Random().nextInt(maxWorkerId + 1); + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_icon.xml b/app/src/main/res/layout/fragment_icon.xml index 456e262..12d777c 100644 --- a/app/src/main/res/layout/fragment_icon.xml +++ b/app/src/main/res/layout/fragment_icon.xml @@ -1,10 +1,11 @@ - + android:background="@drawable/bg_miui10" /> - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + android:layout_height="match_parent" + android:clipToPadding="false" + android:scrollbars="vertical"/> + \ No newline at end of file diff --git a/app/src/main/res/layout/icon_grid_item.xml b/app/src/main/res/layout/icon_grid_item.xml new file mode 100644 index 0000000..b62082d --- /dev/null +++ b/app/src/main/res/layout/icon_grid_item.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file